@venizia/ignis-docs 0.2.0 → 0.2.1-1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (174) hide show
  1. package/README.md +14 -14
  2. package/content/best-practices/api-usage-examples.md +39 -19
  3. package/content/best-practices/architectural-patterns.md +24 -13
  4. package/content/best-practices/architecture-decisions.md +29 -16
  5. package/content/best-practices/code-style-standards/advanced-patterns.md +43 -31
  6. package/content/best-practices/code-style-standards/constants-configuration.md +19 -4
  7. package/content/best-practices/code-style-standards/control-flow.md +33 -1
  8. package/content/best-practices/code-style-standards/documentation.md +28 -8
  9. package/content/best-practices/code-style-standards/function-patterns.md +15 -4
  10. package/content/best-practices/code-style-standards/index.md +7 -2
  11. package/content/best-practices/code-style-standards/naming-conventions.md +27 -3
  12. package/content/best-practices/code-style-standards/route-definitions.md +9 -5
  13. package/content/best-practices/code-style-standards/tooling.md +14 -1
  14. package/content/best-practices/code-style-standards/type-safety.md +39 -6
  15. package/content/best-practices/common-pitfalls.md +59 -35
  16. package/content/best-practices/contribution-workflow.md +8 -4
  17. package/content/best-practices/data-modeling.md +78 -62
  18. package/content/best-practices/deployment-strategies.md +56 -19
  19. package/content/best-practices/error-handling.md +247 -153
  20. package/content/best-practices/index.md +6 -0
  21. package/content/best-practices/performance-optimization.md +26 -13
  22. package/content/best-practices/security-guidelines.md +35 -9
  23. package/content/best-practices/testing-strategies.md +7 -2
  24. package/content/best-practices/troubleshooting-tips.md +27 -17
  25. package/content/extensions/components/api-reference.md +109 -323
  26. package/content/extensions/components/authentication/api.md +489 -602
  27. package/content/extensions/components/authentication/errors.md +135 -498
  28. package/content/extensions/components/authentication/index.md +89 -801
  29. package/content/extensions/components/authentication/usage.md +231 -955
  30. package/content/extensions/components/authorization/api.md +991 -644
  31. package/content/extensions/components/authorization/errors.md +204 -208
  32. package/content/extensions/components/authorization/getting-started.md +227 -0
  33. package/content/extensions/components/authorization/index.md +88 -795
  34. package/content/extensions/components/authorization/usage.md +219 -528
  35. package/content/extensions/components/health-check.md +77 -243
  36. package/content/extensions/components/index.md +24 -90
  37. package/content/extensions/components/mail/api.md +553 -285
  38. package/content/extensions/components/mail/errors.md +76 -62
  39. package/content/extensions/components/mail/index.md +111 -463
  40. package/content/extensions/components/mail/usage.md +139 -173
  41. package/content/extensions/components/request-tracker.md +70 -173
  42. package/content/extensions/components/socket-io/api.md +462 -785
  43. package/content/extensions/components/socket-io/errors.md +49 -51
  44. package/content/extensions/components/socket-io/index.md +69 -372
  45. package/content/extensions/components/socket-io/usage.md +212 -105
  46. package/content/extensions/components/static-asset/api.md +461 -141
  47. package/content/extensions/components/static-asset/errors.md +121 -53
  48. package/content/extensions/components/static-asset/index.md +84 -606
  49. package/content/extensions/components/static-asset/usage.md +184 -299
  50. package/content/extensions/components/template/index.md +3 -3
  51. package/content/extensions/components/websocket/api.md +311 -404
  52. package/content/extensions/components/websocket/errors.md +47 -56
  53. package/content/extensions/components/websocket/index.md +75 -407
  54. package/content/extensions/components/websocket/usage.md +120 -338
  55. package/content/extensions/helpers/cron/index.md +52 -160
  56. package/content/extensions/helpers/crypto/index.md +67 -480
  57. package/content/extensions/helpers/crypto/reference.md +528 -0
  58. package/content/extensions/helpers/env/index.md +64 -178
  59. package/content/extensions/helpers/error/index.md +286 -196
  60. package/content/extensions/helpers/index.md +61 -47
  61. package/content/extensions/helpers/inversion/index.md +76 -550
  62. package/content/extensions/helpers/inversion/reference.md +530 -0
  63. package/content/extensions/helpers/kafka/admin.md +23 -3
  64. package/content/extensions/helpers/kafka/compile-binary.md +83 -52
  65. package/content/extensions/helpers/kafka/consumer.md +65 -28
  66. package/content/extensions/helpers/kafka/examples.md +46 -253
  67. package/content/extensions/helpers/kafka/index.md +55 -617
  68. package/content/extensions/helpers/kafka/producer.md +156 -22
  69. package/content/extensions/helpers/kafka/schema-registry.md +59 -79
  70. package/content/extensions/helpers/logger/hf-logger.md +220 -0
  71. package/content/extensions/helpers/logger/index.md +78 -552
  72. package/content/extensions/helpers/logger/pino.md +105 -0
  73. package/content/extensions/helpers/logger/reference.md +937 -0
  74. package/content/extensions/helpers/network/api.md +276 -195
  75. package/content/extensions/helpers/network/index.md +87 -524
  76. package/content/extensions/helpers/queue/index.md +80 -897
  77. package/content/extensions/helpers/queue/reference.md +494 -0
  78. package/content/extensions/helpers/redis/index.md +86 -640
  79. package/content/extensions/helpers/redis/reference.md +784 -0
  80. package/content/extensions/helpers/secrets/index.md +136 -0
  81. package/content/extensions/helpers/socket-io/api.md +325 -204
  82. package/content/extensions/helpers/socket-io/index.md +79 -429
  83. package/content/extensions/helpers/storage/api.md +584 -464
  84. package/content/extensions/helpers/storage/index.md +80 -573
  85. package/content/extensions/helpers/types/index.md +75 -495
  86. package/content/extensions/helpers/types/reference.md +689 -0
  87. package/content/extensions/helpers/uid/index.md +176 -189
  88. package/content/extensions/helpers/websocket/api.md +366 -214
  89. package/content/extensions/helpers/websocket/index.md +68 -503
  90. package/content/extensions/helpers/worker-thread/index.md +62 -396
  91. package/content/extensions/helpers/worker-thread/reference.md +428 -0
  92. package/content/extensions/index.md +38 -39
  93. package/content/extensions/src-details/mcp-server.md +96 -548
  94. package/content/guides/core-concepts/persistent/datasources.md +2 -2
  95. package/content/guides/core-concepts/persistent/index.md +5 -1
  96. package/content/guides/core-concepts/persistent/models.md +1 -1
  97. package/content/guides/core-concepts/persistent/pglite.md +218 -0
  98. package/content/guides/core-concepts/persistent/postgres-drivers.md +21 -15
  99. package/content/guides/core-concepts/persistent/search-meilisearch.md +22 -12
  100. package/content/guides/core-concepts/persistent/search-typesense.md +28 -16
  101. package/content/guides/core-concepts/persistent/sqlite.md +296 -0
  102. package/content/guides/core-concepts/persistent/transactions.md +12 -11
  103. package/content/guides/core-concepts/secrets-vault.md +177 -0
  104. package/content/guides/core-concepts/services.md +1 -1
  105. package/content/guides/get-started/5-minute-quickstart.md +59 -206
  106. package/content/guides/get-started/philosophy.md +135 -670
  107. package/content/guides/get-started/setup.md +53 -74
  108. package/content/guides/migrations/redis-helpers-migration.md +5 -4
  109. package/content/guides/migrations/scoped-rbac-migration.md +5 -5
  110. package/content/guides/migrations/unified-connectors-migration.md +8 -7
  111. package/content/guides/tutorials/ecommerce-api.md +3 -8
  112. package/content/guides/tutorials/realtime-chat.md +1 -1
  113. package/content/references/base/application.md +64 -22
  114. package/content/references/base/components.md +3 -3
  115. package/content/references/base/connectors.md +79 -136
  116. package/content/references/base/controllers.md +12 -12
  117. package/content/references/base/datasources-reference.md +600 -0
  118. package/content/references/base/datasources.md +84 -444
  119. package/content/references/base/dependency-injection.md +25 -46
  120. package/content/references/base/filter-system/application-usage.md +95 -123
  121. package/content/references/base/filter-system/array-operators.md +33 -43
  122. package/content/references/base/filter-system/comparison-operators.md +55 -63
  123. package/content/references/base/filter-system/default-filter.md +144 -350
  124. package/content/references/base/filter-system/fields-order-pagination.md +116 -148
  125. package/content/references/base/filter-system/index.md +114 -253
  126. package/content/references/base/filter-system/json-filtering.md +52 -181
  127. package/content/references/base/filter-system/list-operators.md +28 -44
  128. package/content/references/base/filter-system/logical-operators.md +69 -114
  129. package/content/references/base/filter-system/null-operators.md +41 -98
  130. package/content/references/base/filter-system/pattern-matching.md +48 -51
  131. package/content/references/base/filter-system/quick-reference.md +93 -196
  132. package/content/references/base/filter-system/range-operators.md +22 -38
  133. package/content/references/base/filter-system/tips.md +70 -133
  134. package/content/references/base/filter-system/use-cases.md +173 -232
  135. package/content/references/base/grpc-controllers.md +53 -17
  136. package/content/references/base/index.md +5 -3
  137. package/content/references/base/middlewares.md +43 -28
  138. package/content/references/base/models-reference.md +886 -0
  139. package/content/references/base/models.md +81 -1452
  140. package/content/references/base/providers.md +8 -8
  141. package/content/references/base/repositories/advanced.md +281 -419
  142. package/content/references/base/repositories/index.md +84 -644
  143. package/content/references/base/repositories/mixins.md +25 -21
  144. package/content/references/base/repositories/relations.md +194 -375
  145. package/content/references/base/repositories/soft-deletable.md +67 -55
  146. package/content/references/base/secrets.md +267 -0
  147. package/content/references/base/services.md +8 -6
  148. package/content/references/configuration/environment-variables.md +73 -21
  149. package/content/references/configuration/index.md +51 -31
  150. package/content/references/index.md +1 -1
  151. package/content/references/quick-reference.md +3 -16
  152. package/content/references/utilities/crypto.md +35 -76
  153. package/content/references/utilities/date.md +33 -73
  154. package/content/references/utilities/duration.md +85 -0
  155. package/content/references/utilities/index.md +6 -2
  156. package/content/references/utilities/jsx-reference.md +298 -0
  157. package/content/references/utilities/jsx.md +82 -525
  158. package/content/references/utilities/module.md +81 -61
  159. package/content/references/utilities/parse.md +34 -64
  160. package/content/references/utilities/performance.md +33 -58
  161. package/content/references/utilities/promise.md +28 -62
  162. package/content/references/utilities/request.md +58 -218
  163. package/content/references/utilities/retry.md +139 -0
  164. package/content/references/utilities/schema.md +43 -137
  165. package/content/references/utilities/statuses-reference.md +361 -0
  166. package/content/references/utilities/statuses.md +63 -667
  167. package/dist/mcp-server/index.js +0 -0
  168. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts +1 -1
  169. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  170. package/dist/mcp-server/tools/github/list-project-files.tool.js +3 -3
  171. package/dist/mcp-server/tools/github/search-code.tool.js +1 -1
  172. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -1
  173. package/dist/mcp-server/tools/github/view-source-file.tool.js +1 -1
  174. package/package.json +24 -23
@@ -1,552 +1,553 @@
1
- # Authentication -- API Reference
1
+ ---
2
+ title: Authentication Reference
3
+ description: Full option tables, binding keys, service class hierarchy, strategy registry, and controller factory for the Authentication component
4
+ difficulty: intermediate
5
+ ---
2
6
 
3
- > Architecture, service hierarchy, strategy registry, JWKS controller, and controller factory. See [Setup & Configuration](./) for initial setup.
7
+ # Authentication Reference
8
+
9
+ Every option, binding key, class, and method the Authentication component exposes. See the [Overview](./) for the guided introduction.
10
+
11
+ **Files:**
12
+
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) |
28
+
29
+ ## Import paths
30
+
31
+ ```typescript
32
+ import {
33
+ // Component + registry
34
+ AuthenticateComponent,
35
+ AuthenticateBindingKeys,
36
+ Authentication,
37
+ AuthenticationFieldCodecs,
38
+ AuthenticationModes,
39
+ AuthenticationTokenTypes,
40
+ AuthenticationStrategyRegistry,
41
+ ServiceAssertion,
42
+
43
+ // JOSE standards + constants
44
+ JOSEStandards,
45
+ JWKSModes,
46
+ JWKSKeyDrivers,
47
+ JWKSKeyFormats,
48
+
49
+ // Strategies
50
+ JWSAuthenticationStrategy,
51
+ JWKSIssuerAuthenticationStrategy,
52
+ JWKSVerifierAuthenticationStrategy,
53
+ BasicAuthenticationStrategy,
54
+ ServiceAuthenticationStrategy,
55
+
56
+ // Services
57
+ AbstractBearerTokenService,
58
+ JWSTokenService,
59
+ JWKSIssuerTokenService,
60
+ JWKSVerifierTokenService,
61
+ BasicTokenService,
62
+
63
+ // Controllers
64
+ defineAuthController,
65
+ JWKSController,
66
+ authenticate,
67
+
68
+ // Entity column helpers
69
+ extraUserColumns,
70
+ extraRoleColumns,
71
+ extraPermissionColumns,
72
+ extraPolicyDefinitionColumns,
73
+ UserStatuses,
74
+ UserTypes,
75
+ RoleStatuses,
76
+ } from '@venizia/ignis';
77
+
78
+ import type {
79
+ TAuthenticationRestOptions,
80
+ TJWTTokenServiceOptions,
81
+ IJWSTokenServiceOptions,
82
+ IJWKSIssuerOptions,
83
+ IJWKSVerifierOptions,
84
+ TJWKSTokenServiceOptions,
85
+ TBasicTokenServiceOptions,
86
+ IServiceAuthOptions,
87
+ IServiceAssertionClaims,
88
+ TServiceCallerEntry,
89
+ IAuthenticateOptions,
90
+ IAuthUser,
91
+ IJWTTokenPayload,
92
+ IPayloadFieldCodec,
93
+ IAuthService,
94
+ IAuthenticationStrategy,
95
+ TDefineAuthControllerOpts,
96
+ TAuthStrategy,
97
+ TAuthMode,
98
+ TGetTokenExpiresFn,
99
+ TJWKSAlgorithm,
100
+ TJWKSKeyDriver,
101
+ TJWKSKeyFormat,
102
+ TJOSEStandard,
103
+ TJWKSMode,
104
+ TPermissionOptions,
105
+ TPermissionCommonColumns,
106
+ TPolicyDefinitionOptions,
107
+ TPolicyDefinitionCommonColumns,
108
+ } from '@venizia/ignis';
109
+ ```
4
110
 
5
111
  ## Architecture
6
112
 
7
113
  ```
8
- ┌──────────────────────────────────────────────────────────────────┐
9
- │ Application │
10
- │ │
11
- │ preConfigure()
12
- │ ├── bind JWT_OPTIONS (TJWTTokenServiceOptions) │
13
- │ │ └── standard: JWS | JWKS │
14
- │ ├── bind BASIC_OPTIONS / REST_OPTIONS │
15
- ├── this.component(AuthenticateComponent) │
16
- └── AuthenticationStrategyRegistry.register() manual │
17
- └────────────────────────────┬─────────────────────────────────────┘
18
-
19
-
20
- ┌──────────────────────────────────────────────────────────────────┐
21
- │ AuthenticateComponent.binding()
22
- │ │
23
- │ 1. Read JWT_OPTIONS, BASIC_OPTIONS, REST_OPTIONS │
24
- │ 2. Switch on jwtOptions.standard:
25
- │ ├── JWS → defineJWSAuth() → JWSTokenService │
26
- │ └── JWKS defineJWKSAuth() → switch on mode: │
27
- │ ├── issuer → JWKSIssuerTokenService │
28
- │ │ + JWKSController (/certs)
29
- └── verifier JWKSVerifierTokenService │
30
- │ 3. defineBasicAuth() → BasicTokenService │
31
- │ 4. defineControllers() → AuthController (factory-built) │
32
- │ 5. defineOAuth2() → stub (not yet implemented) │
33
- └────────────────────────────┬─────────────────────────────────────┘
34
-
35
- ┌─────────────────────┼──────────────────────┐
36
- ▼ ▼ ▼
37
- ┌──────────────┐ ┌────────────────┐ ┌──────────────────┐
38
- │ Bearer Token │ │ BasicToken │ │ AuthController │
39
- │ Services │ │ Service │ │ (factory-built) │
40
- │ (see below) │ └───────┬────────┘ └────────┬─────────┘
41
- └──────┬───────┘ │ │
42
- │ ▼ ▼
43
- │ ┌──────────────┐ ┌──────────────────┐
44
- │ │ Basic │ │ /sign-in │
45
- │ │ Strategy │ │ /sign-up │
46
- │ └──────────────┘ │ /change-password │
47
- │ │ /who-am-i │
48
- ▼ └──────────────────┘
49
- ┌──────────────────────────────────────────────────────────┐
50
- │ Bearer Token Service Hierarchy │
51
- │ │
52
- │ AbstractBearerTokenService (base) │
53
- │ ├── extractCredentials() │
54
- │ ├── verify() → doVerify() │
55
- │ ├── generate() → getSigner() + getSigningKey() │
56
- │ ├── encryptPayload() / decryptPayload() (optional) │
57
- │ │ │
58
- │ ├── JWSTokenService (symmetric HS256) │
59
- │ │ ├── JWSAuthenticationStrategy │
60
- │ │ └── sign with shared secret │
61
- │ │ │
62
- │ └── AbstractJWKSTokenService (lazy-init + retry) │
63
- │ ├── ensureInitialized() / initialize() │
64
- │ │ │
65
- │ ├── JWKSIssuerTokenService (asymmetric) │
66
- │ │ ├── JWKSIssuerAuthenticationStrategy │
67
- │ │ ├── sign with private key │
68
- │ │ ├── verify with public key │
69
- │ │ └── getJWKS() / getJWKSAsync() │
70
- │ │ │
71
- │ └── JWKSVerifierTokenService (remote verify) │
72
- │ ├── JWKSVerifierAuthenticationStrategy │
73
- │ └── verify via createRemoteJWKSet() │
74
- └──────────────────────────────────────────────────────────┘
114
+ Application.preConfigure()
115
+ ├── bind JWT_OPTIONS (TJWTTokenServiceOptions, discriminated on `standard`)
116
+ ├── bind BASIC_OPTIONS / REST_OPTIONS
117
+ ├── this.component(AuthenticateComponent)
118
+ └── AuthenticationStrategyRegistry.register() -- manual, after the component
119
+
120
+ AuthenticateComponent.binding()
121
+ ├── switch on jwtOptions.standard
122
+ ├── JWS -> defineJWSAuth() -> registers JWSTokenService
123
+ │ └── JWKS -> defineJWKSAuth() -> switch on mode
124
+ ├── issuer -> JWKSIssuerTokenService + JWKSController (/certs)
125
+ │ └── verifier -> JWKSVerifierTokenService
126
+ ├── defineBasicAuth() -> registers BasicTokenService (if basicOptions bound)
127
+ ├── defineControllers() -> registers AuthController (if useAuthController: true)
128
+ └── defineOAuth2() -> stub, not implemented
129
+
130
+ Bearer token service hierarchy:
131
+ AbstractBearerTokenService (extractCredentials, verify, generate, encryptPayload/decryptPayload)
132
+ ├── JWSTokenService (symmetric HS256)
133
+ └── AbstractJWKSTokenService (lazy ensureInitialized() + retry-on-failure)
134
+ ├── JWKSIssuerTokenService (sign + verify + getJWKS/getJWKSAsync)
135
+ └── JWKSVerifierTokenService (verify only, via createRemoteJWKSet())
75
136
  ```
76
137
 
77
- ### Tech Stack
138
+ **Tech stack**
78
139
 
79
140
  | Technology | Purpose |
80
141
  |------------|---------|
81
- | **`jose`** | JWT signing (`SignJWT`), verification (`jwtVerify`), JWKS (`createRemoteJWKSet`, `exportJWK`, `importPKCS8`, `importSPKI`, `importJWK`), and type definitions |
82
- | **`@venizia/ignis-helpers`** | `AES` utility for payload encryption, `BaseHelper`/`BaseService` base classes, `getError` for error creation, `HTTP` result codes |
83
- | **Hono middleware** | Route-level authentication integration via `createMiddleware` from `hono/factory` |
84
- | **`node:fs/promises`** | Async file reading for JWKS key files |
85
- | **Drizzle ORM** | Database access for user lookup (in your implementation) |
142
+ | `jose` | JWT signing (`SignJWT`), verification (`jwtVerify`), JWKS (`createRemoteJWKSet`, `exportJWK`, `importPKCS8`, `importSPKI`, `importJWK`) |
143
+ | `@venizia/ignis-helpers` | `AES` payload encryption, `BaseHelper`/`BaseService`, `getError`, `HTTP` result codes |
144
+ | Hono middleware | Route-level integration via `createMiddleware` from `hono/factory` |
145
+ | `node:fs/promises` | Async key file reads for JWKS |
86
146
 
87
- ## Component Methods
147
+ ## Component methods
88
148
 
89
- The `AuthenticateComponent` uses five methods during its `binding()` lifecycle (four private, one public):
149
+ `AuthenticateComponent.binding()` runs four private configuration methods and one public stub:
90
150
 
91
151
  | Method | Purpose |
92
152
  |--------|---------|
93
- | `defineJWSAuth(opts)` | Validates JWS secrets (rejects falsy values and `'unknown_secret'`), validates `getTokenExpiresFn`, binds `IJWSTokenServiceOptions` to `JWT_OPTIONS`, registers `JWSTokenService`. |
94
- | `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`. |
95
- | `defineBasicAuth(opts)` | Validates `verifyCredentials` callback presence, binds `BasicTokenService` as a service. Logs debug if skipped. |
96
- | `defineControllers(opts)` | Requires `jwtOptions` when `useAuthController: true`. Calls `defineAuthController()` factory and registers the generated controller. |
97
- | `defineOAuth2()` | **Public** stub method -- not yet implemented. Called during `binding()` but performs no action. |
153
+ | `defineJWSAuth(opts)` | Validates `jwtSecret` and `getTokenExpiresFn`, binds `IJWSTokenServiceOptions` to `JWT_OPTIONS`, registers `JWSTokenService` |
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 |
156
+ | `defineBasicAuth(opts)` | Validates `verifyCredentials` presence, registers `BasicTokenService`. Skips (debug log) if `basicOptions` not bound |
157
+ | `defineControllers(opts)` | Requires `jwtOptions` when `useAuthController: true`. Calls `defineAuthController()` and registers the generated controller |
158
+ | `defineOAuth2()` | Public stub, called during `binding()`, performs no action - not yet implemented |
98
159
 
99
160
  > [!NOTE]
100
- > The component reads `TJWTTokenServiceOptions` (the discriminated union) from `AuthenticateBindingKeys.JWT_OPTIONS`, then re-binds just the inner options (`IJWSTokenServiceOptions` or `IJWKSIssuerOptions`/`IJWKSVerifierOptions`) to the appropriate binding key for the service to consume via `@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`.
101
162
 
102
- ## Strategy Registry
163
+ ## Binding keys
103
164
 
104
- `AuthenticationStrategyRegistry` is a **singleton** that manages all registered strategies. It extends `AbstractAuthRegistry<IAuthenticationStrategy>` (not generic on `Env`).
165
+ | Constant | Key string | Type | Required | Default |
166
+ |----------|-----------|------|----------|---------|
167
+ | `AuthenticateBindingKeys.REST_OPTIONS` | `@app/authenticate/rest-options` | `TAuthenticationRestOptions` | No | <code v-pre>{ useAuthController: false }</code> |
168
+ | `AuthenticateBindingKeys.JWT_OPTIONS` | `@app/authenticate/jwt-options` | `TJWTTokenServiceOptions` | Conditional | -- |
169
+ | `AuthenticateBindingKeys.JWKS_OPTIONS` | `@app/authenticate/jwks-options` | `IJWKSIssuerOptions \| IJWKSVerifierOptions` | Internal | Bound by the component from `JWT_OPTIONS` |
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` |
105
173
 
106
- ### API
174
+ > [!IMPORTANT]
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.
107
176
 
108
- | Method | Signature | Returns | Description |
109
- |--------|-----------|---------|-------------|
110
- | `getInstance()` | `static` | `AuthenticationStrategyRegistry` | Returns the singleton instance (creates if not exists) |
111
- | `register` | <code v-pre>(opts: { container: Container; strategies: Array&lt;{ name: string; strategy: TClass&lt;IAuthenticationStrategy&gt; }&gt; }) =&gt; this</code> | `this` | Registers strategies as singletons in the container. Returns `this` for chaining. |
112
- | `resolveStrategy` | `(opts: { name: string }) => IAuthenticationStrategy` | `IAuthenticationStrategy` | Resolves a strategy instance from the container by name |
177
+ ## Option interfaces
113
178
 
114
- **Registration:**
115
- ```typescript
116
- AuthenticationStrategyRegistry.getInstance().register({
117
- container: this,
118
- strategies: [
119
- { name: Authentication.STRATEGY_JWT, strategy: JWKSIssuerAuthenticationStrategy },
120
- { name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
121
- ],
122
- });
123
- ```
179
+ ### TJWTTokenServiceOptions
124
180
 
125
- > [!NOTE]
126
- > `register()` returns `this`, enabling method chaining if needed.
127
-
128
- **How it works:**
129
- - Strategies are stored in an internal map and bound to the DI container as singletons
130
- - Binding keys follow the pattern `authentication.strategy.{name}` (e.g., `authentication.strategy.jwt`, `authentication.strategy.basic`)
131
- - The standalone `authenticate()` function creates middleware via `AuthenticationProvider`, which uses the registry to resolve strategies
132
-
133
- **Middleware creation:**
134
-
135
- The `authenticate()` function returns a Hono middleware that:
136
- 1. Checks if `Authentication.SKIP_AUTHENTICATION` is set on context - if true, skips entirely (logs debug)
137
- 2. Checks if `Authentication.CURRENT_USER` is already set on context - if true, skips (already authenticated)
138
- 3. Reads `strategies` and `mode` from the provided options
139
- 4. Executes strategies based on mode (`any` or `all`)
140
- 5. On success, sets `Authentication.CURRENT_USER` and `Authentication.AUDIT_USER_ID` on context
141
- 6. On failure, throws 401 with list of tried strategies
142
-
143
- ```mermaid
144
- sequenceDiagram
145
- participant C as Client
146
- participant MW as Auth Middleware
147
- participant P as AuthenticationProvider
148
- participant R as StrategyRegistry
149
- participant S as Strategy
150
- participant SVC as TokenService
151
-
152
- C->>MW: Request with Authorization header
153
- MW->>P: authenticateFn({ strategies, mode })
154
- P->>P: Check SKIP_AUTHENTICATION
155
- P->>P: Check CURRENT_USER already set
156
- P->>R: resolveStrategy({ name })
157
- R-->>P: strategy instance
158
- P->>S: authenticate(context)
159
- S->>SVC: extractCredentials(context)
160
- SVC-->>S: { type, token }
161
- S->>SVC: verify({ type, token })
162
- SVC-->>S: IJWTTokenPayload
163
- S-->>P: IAuthUser
164
- P->>MW: Set CURRENT_USER + AUDIT_USER_ID
165
- MW->>C: Continue to handler
166
- ```
167
-
168
- ### Standalone `authenticate()` Function
181
+ Discriminated union on `standard`:
169
182
 
170
183
  ```typescript
171
- const authenticationProvider = new AuthenticationProvider();
172
- const authenticateFn = authenticationProvider.value();
184
+ type TJWTTokenServiceOptions =
185
+ | { standard: typeof JOSEStandards.JWS; options: IJWSTokenServiceOptions }
186
+ | { standard: typeof JOSEStandards.JWKS; options: TJWKSTokenServiceOptions };
173
187
 
174
- export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) => {
175
- return authenticateFn(opts);
176
- };
188
+ type TJWKSTokenServiceOptions = IJWKSIssuerOptions | IJWKSVerifierOptions; // discriminated on `mode`
177
189
  ```
178
190
 
179
- This is the primary export for creating auth middleware. It creates an `AuthenticationProvider` instance and calls `.value()` to get the middleware factory. The provider uses `AuthenticationStrategyRegistry.getInstance()` internally to resolve strategies.
191
+ ### IJWSTokenServiceOptions
192
+
193
+ | Option | Type | Default | Required | Description |
194
+ |--------|------|---------|----------|-------------|
195
+ | `jwtSecret` | `string` | -- | Yes | Secret for signing and verifying the JWT signature |
196
+ | `getTokenExpiresFn` | `TGetTokenExpiresFn` | -- | Yes | Returns token expiration in seconds |
197
+ | `applicationSecret` | `string` | -- | No | Enables AES payload field encryption when set |
198
+ | `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
199
+ | `headerAlgorithm` | `string` | `'HS256'` | No | JWT signing algorithm |
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 |
203
+
204
+ > [!WARNING]
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.
206
+
207
+ ### IJWKSIssuerOptions
208
+
209
+ | Option | Type | Default | Required | Description |
210
+ |--------|------|---------|----------|-------------|
211
+ | `mode` | `typeof JWKSModes.ISSUER` | -- | Yes | Must be `'issuer'` |
212
+ | `algorithm` | `TJWKSAlgorithm` | -- | Yes | `'ES256'`, `'RS256'`, or `'EdDSA'` |
213
+ | `keys.driver` | `TJWKSKeyDriver` | -- | Yes | `'text'` (inline) or `'file'` (path) |
214
+ | `keys.format` | `TJWKSKeyFormat` | -- | Yes | `'pem'` or `'jwk'` |
215
+ | `keys.private` | `string` | -- | Yes | Private key content or file path |
216
+ | `keys.public` | `string` | -- | Yes | Public key content or file path |
217
+ | `kid` | `string` | -- | Yes | Key ID exposed in the JWKS endpoint and JWT header |
218
+ | `getTokenExpiresFn` | `TGetTokenExpiresFn` | -- | Yes | Returns token expiration in seconds |
219
+ | `rest.path` | `string` | `'/certs'` | No | Path of the generated `JWKSController` |
220
+ | `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload encryption |
221
+ | `applicationSecret` | `string` | -- | No | Enables AES payload field encryption when set |
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 |
225
+
226
+ ### IJWKSVerifierOptions
227
+
228
+ | Option | Type | Default | Required | Description |
229
+ |--------|------|---------|----------|-------------|
230
+ | `mode` | `typeof JWKSModes.VERIFIER` | -- | Yes | Must be `'verifier'` |
231
+ | `jwksUrl` | `string` | -- | Yes | URL of the issuer's JWKS endpoint |
232
+ | `cacheTtlMs` | `number` | `43_200_000` (12h) | No | `createRemoteJWKSet` `cacheMaxAge` |
233
+ | `cooldownMs` | `number` | `30_000` (30s) | No | `createRemoteJWKSet` `cooldownDuration` |
234
+ | `aesAlgorithm` | `AESAlgorithmType` | `'aes-256-cbc'` | No | AES algorithm for payload decryption |
235
+ | `applicationSecret` | `string` | -- | No | Must match the issuer's secret to decrypt payloads |
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 |
238
+
239
+ > [!IMPORTANT]
240
+ > `JWKSVerifierTokenService` cannot sign tokens - `getSigner()`, `getSigningKey()`, and `getDefaultTokenExpiresFn()` all throw. Only `verify()` and `extractCredentials()` are functional.
241
+
242
+ ### TBasicTokenServiceOptions
243
+
244
+ | Option | Type | Description |
245
+ |--------|------|-------------|
246
+ | `verifyCredentials` | <code v-pre>(opts: { credentials: { username: string; password: string }; context: TContext }) =&gt; Promise&lt;IAuthUser \| null&gt;</code> | Callback that validates Basic credentials and returns the authenticated user, or `null` |
247
+
248
+ ### TAuthenticationRestOptions
249
+
250
+ Discriminated union on `useAuthController` - when `true`, `controllerOpts` becomes required:
180
251
 
181
- > [!NOTE]
182
- > In `all` mode, the **first** strategy's user payload is used as the identity source - all strategies must succeed but the first one wins for identity. If every strategy passes but the first user payload has no `userId`, the middleware throws a `401` with message `"Failed to identify authenticated user!"`. The `any` mode **discards errors** from each failing strategy (logs at debug level) and only throws after all strategies are exhausted.
183
-
184
- ## Service Class Hierarchy
185
-
186
- ```mermaid
187
- classDiagram
188
- class AbstractBearerTokenService {
189
- <<abstract>>
190
- #aes: AES | null
191
- #applicationSecret: string | null
192
- +extractCredentials(context) credentials
193
- +verify(opts) IJWTTokenPayload
194
- +generate(opts) string
195
- +encryptPayload(payload) Record
196
- +decryptPayload(opts) IJWTTokenPayload
197
- #doVerify(token)* IJWTTokenPayload
198
- +getSigner(opts)* SignJWT
199
- #getSigningKey()* Uint8Array | CryptoKey
200
- #getDefaultTokenExpiresFn()* TGetTokenExpiresFn
201
- }
202
-
203
- class JWSTokenService {
204
- #jwtSecret: Uint8Array
205
- #options: IJWSTokenServiceOptions
206
- }
207
-
208
- class AbstractJWKSTokenService {
209
- <<abstract>>
210
- #initialized: boolean
211
- #initPromise: Promise | null
212
- +ensureInitialized()
213
- #initialize()* void
214
- }
215
-
216
- class JWKSIssuerTokenService {
217
- #privateKey: CryptoKey | null
218
- #publicKey: CryptoKey | null
219
- #jwks: object | null
220
- +getJWKS() keys
221
- +getJWKSAsync() keys
222
- }
223
-
224
- class JWKSVerifierTokenService {
225
- #jwksVerifier: Function | null
226
- +getSigner() never
227
- +getSigningKey() never
228
- }
229
-
230
- AbstractBearerTokenService <|-- JWSTokenService
231
- AbstractBearerTokenService <|-- AbstractJWKSTokenService
232
- AbstractJWKSTokenService <|-- JWKSIssuerTokenService
233
- AbstractJWKSTokenService <|-- JWKSVerifierTokenService
252
+ ```typescript
253
+ type TAuthenticationRestOptions = {} & (
254
+ | { useAuthController?: false | undefined }
255
+ | { useAuthController: true; controllerOpts: TDefineAuthControllerOpts }
256
+ );
234
257
  ```
235
258
 
236
- ## AbstractBearerTokenService
259
+ ### TDefineAuthControllerOpts
237
260
 
238
- Base class for all Bearer token services. Extends `BaseService`. Generic on <code v-pre>&lt;E extends Env = Env&gt;</code>.
261
+ | Option | Type | Default | Description |
262
+ |--------|------|---------|-------------|
263
+ | `restPath` | `string` | `'/auth'` | Base path for the generated controller |
264
+ | `serviceKey` | `string` | -- | DI key for the `IAuthService` implementation (required) |
265
+ | `requireAuthenticatedSignUp` | `boolean` | `false` | Whether `POST /sign-up` requires a valid JWT |
266
+ | `payload.signIn` | `{ request: { schema }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/sign-in` |
267
+ | `payload.signUp` | `{ request: { schema }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/sign-up` |
268
+ | `payload.changePassword` | `{ request: { schema? }, response: { schema } }` | Built-in schemas | Custom Zod schemas for `/change-password` |
269
+ | `payload.refreshToken` | `{ response: { schema } }` | `AnyObjectSchema` | Custom response schema for `/token/refresh` |
270
+ | `payload.getUserInformation` | `{ response: { schema } }` | `AnyObjectSchema` | Custom response schema for `/me` and the `who-am-i` `userInformation` field |
239
271
 
240
- **File:** `packages/core/src/components/auth/authenticate/services/bearer/abstract.service.ts`
272
+ ### Route authenticate config
241
273
 
242
- ### Static Fields
243
-
244
- | Field | Type | Description |
245
- |-------|------|-------------|
246
- | `JWT_COMMON_FIELDS` | <code v-pre>Set&lt;'iss' \| 'sub' \| 'aud' \| 'jti' \| 'nbf' \| 'exp' \| 'iat'&gt;</code> | Standard JWT fields that are never encrypted |
247
-
248
- ### Protected Fields
274
+ ```typescript
275
+ type TRouteAuthenticateConfig =
276
+ | { skip: true }
277
+ | { skip?: false; strategies?: TAuthStrategy[]; mode?: TAuthMode };
278
+ ```
249
279
 
250
280
  | Field | Type | Default | Description |
251
281
  |-------|------|---------|-------------|
252
- | `aes` | `AES \| null` | `null` | AES utility instance, configured by `configurePayloadEncryption()` |
253
- | `applicationSecret` | `string \| null` | `null` | AES secret, configured by `configurePayloadEncryption()` |
254
- | `fieldCodecs` | <code v-pre>Map&lt;string, IPayloadFieldCodec&gt;</code> | `new Map()` | Field codec map keyed by field name, configured by `configurePayloadEncryption()` |
255
-
256
- ### Methods
257
-
258
- | Method | Signature | Description |
259
- |--------|-----------|-------------|
260
- | `configurePayloadEncryption` | <code v-pre>(opts: { aesAlgorithm?: AESAlgorithmType; applicationSecret?: string; fieldCodecs?: IPayloadFieldCodec[] }) =&gt; void</code> | Configures optional AES encryption and field codecs. Codecs are converted to a Map keyed by `codec.key` for O(1) lookup. |
261
- | `extractCredentials` | <code v-pre>(context: TContext&lt;E, string&gt;) =&gt; { type: string; token: string }</code> | Extracts Bearer token from Authorization header |
262
- | `verify` | <code v-pre>(opts: { type: string; token: string }) =&gt; Promise&lt;IJWTTokenPayload&gt;</code> | Template method - calls `doVerify()` |
263
- | `generate` | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn?: TGetTokenExpiresFn }) =&gt; Promise&lt;string&gt;</code> | Template method - calls `getSigner()` + `getSigningKey()` |
264
- | `serializeField` | <code v-pre>(opts: { key: string; value: any }) =&gt; string</code> | Serializes a single field: codec → `JSON.stringify` fallback |
265
- | `deserializeField` | <code v-pre>(opts: { key: string; value: string }) =&gt; any</code> | Deserializes a single field: codec → `JSON.parse` fallback |
266
- | `encryptPayload` | <code v-pre>(payload: IJWTTokenPayload) =&gt; Record&lt;string, any&gt;</code> | AES-encrypts non-standard JWT fields using `serializeField`. Returns payload unchanged if AES not configured. |
267
- | `decryptPayload` | <code v-pre>(opts: { result: JWTVerifyResult&lt;IJWTTokenPayload&gt; }) =&gt; IJWTTokenPayload</code> | Decrypts AES-encrypted fields using `deserializeField`. Returns payload unchanged if AES not configured. |
268
-
269
- ### Abstract Methods (implemented by subclasses)
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 |
283
+ | `authenticate.mode` | `'any' \| 'all'` | `'any'` | `'any'`: first success wins. `'all'`: every strategy must pass |
284
+ | `authenticate.skip` | `true` | -- | Skips authentication for this route entirely |
270
285
 
271
- | Method | Visibility | Signature | Description |
272
- |--------|------------|-----------|-------------|
273
- | `doVerify` | `protected` | `(token: string) => Promise<IJWTTokenPayload>` | Verify the raw JWT token and return the payload |
274
- | `getSigner` | **`public`** | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn: TGetTokenExpiresFn }) =&gt; Promise&lt;SignJWT&gt;</code> | Create a `jose.SignJWT` instance with the payload |
275
- | `getSigningKey` | `protected` | `() => ValueOrPromise<Uint8Array \| CryptoKey>` | Return the signing key |
276
- | `getDefaultTokenExpiresFn` | `protected` | `() => TGetTokenExpiresFn` | Return the default token expiry function |
286
+ ### IAuthUser / IJWTTokenPayload
277
287
 
278
- ## JWSTokenService
279
-
280
- Symmetric JWT (HS256) token service with optional AES-encrypted payloads. Extends `AbstractBearerTokenService`.
288
+ ```typescript
289
+ interface IAuthUser {
290
+ userId: IdType;
291
+ [extra: string | symbol]: any;
292
+ }
281
293
 
282
- **File:** `packages/core/src/components/auth/authenticate/services/bearer/jws.service.ts`
294
+ interface IJWTTokenPayload extends JWTPayload, IAuthUser {
295
+ userId: IdType;
296
+ roles: { id: IdType; identifier: string; priority: number }[];
297
+ clientId?: string;
298
+ provider?: string;
299
+ email?: string;
300
+ name?: string;
301
+ [extra: string | symbol]: any;
302
+ }
303
+ ```
283
304
 
284
- ### JWSAuthenticationStrategy
305
+ > [!TIP]
306
+ > `IAuthUser` is intentionally minimal. Your `IAuthService` can return extra fields (roles, email, provider) - they pass through JWT generation and are available on `Authentication.CURRENT_USER` after authentication.
285
307
 
286
- Extends `BaseHelper` and implements <code v-pre>IAuthenticationStrategy&lt;E&gt;</code>.
308
+ ### IAuthService
287
309
 
288
310
  ```typescript
289
- class JWSAuthenticationStrategy<E extends Env = Env>
290
- extends BaseHelper
291
- implements IAuthenticationStrategy<E>
292
- {
293
- name = Authentication.STRATEGY_JWT;
294
- standard = JOSEStandards.JWS;
295
-
296
- constructor(
297
- @inject({
298
- key: BindingKeys.build({
299
- namespace: BindingNamespaces.SERVICE,
300
- key: JWSTokenService.name,
301
- }),
302
- })
303
- private service: JWSTokenService<E>,
304
- ) { ... }
305
-
306
- authenticate(context: TContext<E, string>): Promise<IAuthUser> {
307
- const token = this.service.extractCredentials(context);
308
- return this.service.verify(token);
309
- }
311
+ interface IAuthService<
312
+ E extends Env = Env,
313
+ SIRQ extends TSignInRequest = TSignInRequest, SIRS = AnyObject,
314
+ SURQ extends TSignUpRequest = TSignUpRequest, SURS = AnyObject,
315
+ CPRQ extends TChangePasswordRequest = TChangePasswordRequest, CPRS = AnyObject,
316
+ UIRQ = AnyObject, UIRS = AnyObject,
317
+ RTRS = AnyObject,
318
+ > {
319
+ signIn(context: TContext<E>, opts: SIRQ): Promise<SIRS>;
320
+ signUp(context: TContext<E>, opts: SURQ): Promise<SURS>;
321
+ changePassword(context: TContext<E>, opts: CPRQ): Promise<CPRS>;
322
+ getUserInformation?(context: TContext<E>, opts: UIRQ): Promise<UIRS>;
323
+ refreshToken?(context: TContext<E>): Promise<RTRS>;
310
324
  }
311
325
  ```
312
326
 
313
- ### Protected Fields
327
+ > [!NOTE]
328
+ > `getUserInformation` and `refreshToken` are optional. The generated auth controller returns `501` for `/me`, `/token/refresh`, or `?withUserInformation=true` on `/who-am-i` if the bound service doesn't implement the corresponding method.
314
329
 
315
- | Field | Type | Description |
316
- |-------|------|-------------|
317
- | `jwtSecret` | `Uint8Array` | Encoded JWT secret for `jose` signing/verification |
318
- | `options` | `IJWSTokenServiceOptions` | Injected options |
330
+ ### Field codecs
319
331
 
320
- ### Constructor Behavior
332
+ ```typescript
333
+ interface IPayloadFieldCodec<T = unknown> {
334
+ key: string;
335
+ serialize(opts: { value: T }): string;
336
+ deserialize(opts: { raw: string }): T;
337
+ }
338
+ ```
321
339
 
322
- The constructor validates required options and throws immediately (status 500) if any are missing:
340
+ `AuthenticationFieldCodecs.ROLES_CODEC` is a ready-made codec for the `roles` field (pipe-separated `id|identifier|priority` strings). It is **not applied automatically** - pass it explicitly via `fieldCodecs: [AuthenticationFieldCodecs.ROLES_CODEC]` in the JWS/JWKS options. Without it, `roles` (and every other non-standard field) is serialized with plain `JSON.stringify` before AES encryption.
323
341
 
324
342
  ```typescript
325
- constructor(
326
- @inject({ key: AuthenticateBindingKeys.JWT_OPTIONS })
327
- protected options: IJWSTokenServiceOptions,
328
- ) {
329
- // Throws '[JWSTokenService] Invalid jwtSecret' if !jwtSecret
330
- // Throws '[JWSTokenService] Invalid getTokenExpiresFn' if !getTokenExpiresFn
331
- // Calls configurePayloadEncryption({ aesAlgorithm, applicationSecret })
332
- // Encodes jwtSecret to Uint8Array for jose
343
+ class AuthenticationFieldCodecs {
344
+ static readonly ROLES_CODEC: IPayloadFieldCodec<IJWTTokenPayload['roles']>;
345
+ static build<T>(opts: { key: string; serialize; deserialize }): IPayloadFieldCodec<T>;
333
346
  }
334
347
  ```
335
348
 
336
- > [!NOTE]
337
- > `applicationSecret` is no longer validated in the constructor. If not provided, AES encryption is simply not configured, and payloads pass through in plaintext.
349
+ ## Context variables
338
350
 
339
- ### Overridden Methods
351
+ Set on the Hono `Context` during authentication, readable via `context.get()`:
340
352
 
341
- | Method | Behavior |
342
- |--------|----------|
343
- | `doVerify(token)` | Calls `jwtVerify(token, this.jwtSecret)`, then `this.decryptPayload()` |
344
- | `getSigner(opts)` | Calls `this.encryptPayload()`, then creates `SignJWT` with HS256 header |
345
- | `getSigningKey()` | Returns `this.jwtSecret` |
346
- | `getDefaultTokenExpiresFn()` | Returns `this.options.getTokenExpiresFn` |
353
+ | Constant | Key string | Type | Description |
354
+ |----------|-----------|------|-------------|
355
+ | `Authentication.CURRENT_USER` | `auth.current.user` | `IAuthUser` | Authenticated user payload |
356
+ | `Authentication.AUDIT_USER_ID` | `audit.user.id` | `IdType` | Authenticated user's ID |
357
+ | `Authentication.SKIP_AUTHENTICATION` | `authentication.skip` | `boolean` | Set `true` in a preceding middleware to bypass auth |
347
358
 
348
- ## AbstractJWKSTokenService
359
+ ## Constants
349
360
 
350
- Base class for JWKS token services (Issuer + Verifier). Extends `AbstractBearerTokenService`.
361
+ **Authentication**
351
362
 
352
- **File:** `packages/core/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts`
363
+ | Constant | Value | Description |
364
+ |----------|-------|-------------|
365
+ | `Authentication.STRATEGY_JWT` | `'jwt'` | JWT strategy name |
366
+ | `Authentication.STRATEGY_BASIC` | `'basic'` | Basic strategy name |
367
+ | `Authentication.TYPE_BEARER` | `'Bearer'` | Bearer token type prefix |
368
+ | `Authentication.TYPE_BASIC` | `'Basic'` | Basic token type prefix |
369
+ | `Authentication.AUTHENTICATION_STRATEGY` | `'authentication.strategy'` | Binding key prefix for registered strategies |
353
370
 
354
- Consolidates the lazy-initialization pattern with retry-on-failure semantics. If `initialize()` rejects, `initPromise` is reset so the next call retries instead of caching the failure permanently.
371
+ **AuthenticationTokenTypes**
355
372
 
356
- ### Protected Fields
373
+ | Constant | Value |
374
+ |----------|-------|
375
+ | `TYPE_AUTHORIZATION_CODE` | `'000_AUTHORIZATION_CODE'` |
376
+ | `TYPE_ACCESS_TOKEN` | `'100_ACCESS_TOKEN'` |
377
+ | `TYPE_REFRESH_TOKEN` | `'200_REFRESH_TOKEN'` |
357
378
 
358
- | Field | Type | Default | Description |
359
- |-------|------|---------|-------------|
360
- | `initialized` | `boolean` | `false` | Whether the service has been initialized |
361
- | `initPromise` | `Promise<void> \| null` | `null` | Pending initialization promise (for concurrent callers) |
379
+ **JOSE / JWKS constants** - each class also exposes `SCHEME_SET: Set<string>` and `isValid(input): boolean`
362
380
 
363
- ### Methods
381
+ | Class | Members |
382
+ |-------|---------|
383
+ | `JOSEStandards` | `JWS` (`'JWS'`), `JWKS` (`'JWKS'`) |
384
+ | `JWKSModes` | `ISSUER` (`'issuer'`), `VERIFIER` (`'verifier'`) |
385
+ | `JWKSKeyDrivers` | `TEXT` (`'text'`), `FILE` (`'file'`) |
386
+ | `JWKSKeyFormats` | `PEM` (`'pem'`), `JWK` (`'jwk'`) |
387
+ | `AuthenticateStrategy` | `BASIC` (`'basic'`), `JWT` (`'jwt'`) - same values as `Authentication.STRATEGY_*` |
388
+ | `AuthenticationModes` | `ANY` (`'any'`), `ALL` (`'all'`) |
364
389
 
365
- | Method | Signature | Description |
366
- |--------|-----------|-------------|
367
- | `ensureInitialized` | `() => Promise<void>` | Lazily initializes the service on first call. Concurrent callers share the same promise. On failure, resets `initPromise` so the next call retries. |
390
+ ## Strategy registry
368
391
 
369
- ### Abstract Methods
392
+ `AuthenticationStrategyRegistry` is a singleton extending `AbstractAuthRegistry<IAuthenticationStrategy>`.
370
393
 
371
394
  | Method | Signature | Description |
372
- |--------|-----------|-------------|
373
- | `initialize` | `() => Promise<void>` | Perform one-time async initialization (load keys, create verifier, etc.) |
374
-
375
- ## JWKSIssuerTokenService
395
+ |--------|-----------|--------------|
396
+ | `getInstance()` | `static (): AuthenticationStrategyRegistry` | Returns (creating if needed) the singleton |
397
+ | `register` | <code v-pre>(opts: { container: Container; strategies: { name: string; strategy: TClass&lt;IAuthenticationStrategy&gt; }[] }) =&gt; this</code> | Binds each strategy into the container as a singleton under `authentication.strategy.<name>`. Returns `this` |
398
+ | `resolveStrategy` | `(opts: { name: string }) => IAuthenticationStrategy` | Resolves a registered strategy instance by name |
376
399
 
377
- Asymmetric JWT token service (issuer mode). Extends `AbstractJWKSTokenService`.
378
-
379
- **File:** `packages/core/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts`
400
+ ```typescript
401
+ AuthenticationStrategyRegistry.getInstance().register({
402
+ container: this,
403
+ strategies: [
404
+ { name: Authentication.STRATEGY_JWT, strategy: JWKSIssuerAuthenticationStrategy },
405
+ { name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
406
+ ],
407
+ });
408
+ ```
380
409
 
381
- ### JWKSIssuerAuthenticationStrategy
410
+ **Standalone `authenticate()` function** - the primary export for creating middleware outside the route-config `authenticate` field:
382
411
 
383
412
  ```typescript
384
- class JWKSIssuerAuthenticationStrategy<E extends Env = Env>
385
- extends BaseHelper
386
- implements IAuthenticationStrategy<E>
387
- {
388
- name = Authentication.STRATEGY_JWT;
389
- standard = JOSEStandards.JWKS;
390
-
391
- constructor(
392
- @inject({
393
- key: BindingKeys.build({
394
- namespace: BindingNamespaces.SERVICE,
395
- key: JWKSIssuerTokenService.name,
396
- }),
397
- })
398
- private service: JWKSIssuerTokenService<E>,
399
- ) { ... }
400
-
401
- authenticate(context: TContext<E, string>): Promise<IAuthUser> {
402
- const token = this.service.extractCredentials(context);
403
- return this.service.verify(token);
404
- }
405
- }
413
+ const authenticationProvider = new AuthenticationProvider();
414
+ const authenticateFn = authenticationProvider.value();
415
+
416
+ export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) => authenticateFn(opts);
406
417
  ```
407
418
 
408
- ### Protected Fields
419
+ **`AuthenticationProvider` middleware behavior:**
409
420
 
410
- | Field | Type | Default | Description |
411
- |-------|------|---------|-------------|
412
- | `privateKey` | `CryptoKey \| Uint8Array \| null` | `null` | Private key for signing (loaded during `initialize`) |
413
- | `publicKey` | `CryptoKey \| Uint8Array \| null` | `null` | Public key for verification (loaded during `initialize`) |
414
- | `jwks` | `{ keys: JWK[] } \| null` | `null` | Cached JWKS for the `/certs` endpoint |
421
+ 1. If `Authentication.SKIP_AUTHENTICATION` is set on context, skips entirely (debug log)
422
+ 2. If `Authentication.CURRENT_USER` is already set, skips (already authenticated)
423
+ 3. Runs strategies per `mode` (`'any'` or `'all'`)
424
+ 4. On success, sets `Authentication.CURRENT_USER` and `Authentication.AUDIT_USER_ID`
425
+ 5. On failure, throws `401`
415
426
 
416
- ### Constructor Behavior
427
+ > [!NOTE]
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.
417
429
 
418
- ```typescript
419
- constructor(
420
- @inject({ key: AuthenticateBindingKeys.JWKS_OPTIONS })
421
- protected options: IJWKSIssuerOptions,
422
- ) {
423
- // Calls configurePayloadEncryption({ aesAlgorithm, applicationSecret })
424
- // Keys are NOT loaded here - loaded lazily via ensureInitialized()
425
- }
430
+ ## Service class hierarchy
431
+
432
+ ```
433
+ AbstractBearerTokenService<E> (extends BaseService)
434
+ #aes, #applicationSecret, #fieldCodecs
435
+ +extractCredentials(context) Extract Bearer token from Authorization header
436
+ +verify(opts) Template method -> doVerify()
437
+ +generate(opts) Template method -> getSigner() + getSigningKey()
438
+ +encryptPayload(payload) / decryptPayload(opts)
439
+ #doVerify(token)* +getSigner(opts)* #getSigningKey()* #getDefaultTokenExpiresFn()*
440
+
441
+ JWSTokenService Symmetric HS256, shared secret
442
+ used by JWSAuthenticationStrategy
443
+
444
+ AbstractJWKSTokenService Lazy ensureInitialized() + retry-on-failure
445
+ #initialized, #initPromise
446
+ +ensureInitialized() #initialize()*
447
+
448
+ JWKSIssuerTokenService Sign with private key, verify with public key
449
+ +getJWKS() (sync, throws if uninitialized) +getJWKSAsync()
450
+ used by JWKSIssuerAuthenticationStrategy
451
+
452
+ JWKSVerifierTokenService Verify only, via remote JWKS
453
+ used by JWKSVerifierAuthenticationStrategy
426
454
  ```
427
455
 
428
- ### Initialization Flow
456
+ ### AbstractBearerTokenService
429
457
 
430
- The `initialize()` method:
431
- 1. **Resolves key content** - reads from file (`readFile` from `node:fs/promises`) or uses inline text, based on `keys.driver`
432
- 2. **Parses key material** - imports keys using `importPKCS8`/`importSPKI` (PEM format) or `importJWK` (JWK format), based on `keys.format`
433
- 3. **Exports public JWK** - calls `exportJWK()` and adds `kid`, `alg`, `use: 'sig'` metadata
434
- 4. **Caches JWKS** - stores `{ keys: [publicJWK] }` for the `/certs` endpoint
435
- 5. **Sets `initialized = true`**
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)
436
459
 
437
- ### Overridden Methods
460
+ | Method | Signature | Description |
461
+ |--------|-----------|-------------|
462
+ | `configurePayloadEncryption` | <code v-pre>(opts: { aesAlgorithm?; applicationSecret?; fieldCodecs?: IPayloadFieldCodec[] }) =&gt; void</code> | Sets up AES + field codecs. AES only activates if `applicationSecret` is provided |
463
+ | `extractCredentials` | `(context) => { type: string; token: string }` | Parses `Authorization: Bearer <token>` |
464
+ | `verify` | `(opts: { type, token }) => Promise<IJWTTokenPayload>` | Calls `doVerify()`, wraps errors as sanitized `401` |
465
+ | `generate` | `(opts: { payload, getTokenExpiresFn? }) => Promise<string>` | Calls `getSigner()` then signs with `getSigningKey()` |
466
+ | `serializeField` / `deserializeField` | `(opts) => string` / `any` | Per-field codec lookup, `JSON.stringify`/`JSON.parse` fallback |
467
+ | `encryptPayload` | `(payload) => Record<string, any>` | AES-encrypts non-standard fields (keys and values). No-op if AES not configured |
468
+ | `decryptPayload` | `(opts: { result }) => IJWTTokenPayload` | Reverses `encryptPayload`. No-op if AES not configured |
438
469
 
439
- | Method | Behavior |
440
- |--------|----------|
441
- | `doVerify(token)` | Calls `ensureInitialized()`, then `jwtVerify(token, this.publicKey!)`, then `this.decryptPayload()` |
442
- | `getSigner(opts)` | Calls `ensureInitialized()`, then `this.encryptPayload()`, then creates `SignJWT` with algorithm + kid header |
443
- | `getSigningKey()` | Returns `this.privateKey` |
444
- | `getDefaultTokenExpiresFn()` | Returns `this.options.getTokenExpiresFn` |
470
+ Static: `JWT_COMMON_FIELDS: Set<'iss'|'sub'|'aud'|'jti'|'nbf'|'exp'|'iat'>` - never encrypted or touched by field codecs.
445
471
 
446
- ### JWKS Methods
472
+ ### JWSTokenService
447
473
 
448
- | Method | Signature | Description |
449
- |--------|-----------|-------------|
450
- | `getJWKS` | `() => { keys: JWK[] }` | Synchronous - returns cached JWKS. Throws if not yet initialized. |
451
- | `getJWKSAsync` | `() => Promise<{ keys: JWK[] }>` | Async - calls `ensureInitialized()` first, then returns JWKS. |
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)
475
+
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.
477
+
478
+ ### AbstractJWKSTokenService
479
+
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)
481
+
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.
452
483
 
453
- ### Internal Methods
484
+ ### JWKSIssuerTokenService
485
+
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)
487
+
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] }`.
454
494
 
455
495
  | Method | Signature | Description |
456
496
  |--------|-----------|-------------|
457
- | `resolveKeyContent` | `(opts: { keys }) => Promise<{ priv: string; pub: string }>` | Reads key content from file or returns inline text |
458
- | `parseKeyMaterial` | `(opts: { raw, algorithm, keys }) => Promise<{ priv, pub }>` | Imports keys using `jose` based on format (PEM or JWK) |
497
+ | `getJWKS` | `() => { keys: JWK[] }` | Synchronous, returns the cached JWKS. Throws if called before `initialize()` completes |
498
+ | `getJWKSAsync` | `() => Promise<{ keys: JWK[] }>` | Calls `ensureInitialized()` first |
459
499
 
460
- ## JWKSVerifierTokenService
500
+ ### JWKSVerifierTokenService
461
501
 
462
- Asymmetric JWT token service (verifier mode). Extends `AbstractJWKSTokenService`.
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)
463
503
 
464
- **File:** `packages/core/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts`
504
+ `initialize()` calls `createRemoteJWKSet(jwksUrl, { cacheMaxAge: cacheTtlMs ?? 43_200_000, cooldownDuration: cooldownMs ?? 30_000 })`. `getSigner`/`getSigningKey`/`getDefaultTokenExpiresFn` all throw - this service is verify-only.
465
505
 
466
- ### JWKSVerifierAuthenticationStrategy
506
+ ### BasicTokenService
467
507
 
468
- ```typescript
469
- class JWKSVerifierAuthenticationStrategy<E extends Env = Env>
470
- extends BaseHelper
471
- implements IAuthenticationStrategy<E>
472
- {
473
- name = Authentication.STRATEGY_JWT;
474
- standard = JOSEStandards.JWKS;
475
-
476
- constructor(
477
- @inject({
478
- key: BindingKeys.build({
479
- namespace: BindingNamespaces.SERVICE,
480
- key: JWKSVerifierTokenService.name,
481
- }),
482
- })
483
- private service: JWKSVerifierTokenService<E>,
484
- ) { ... }
485
-
486
- authenticate(context: TContext<E, string>): Promise<IAuthUser> {
487
- const token = this.service.extractCredentials(context);
488
- return this.service.verify(token);
489
- }
490
- }
491
- ```
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)
492
509
 
493
- ### Protected Fields
494
-
495
- | Field | Type | Default | Description |
496
- |-------|------|---------|-------------|
497
- | `jwksVerifier` | `ReturnType<typeof createRemoteJWKSet> \| null` | `null` | Remote JWKS verifier function |
510
+ | Method | Signature | Description |
511
+ |--------|-----------|-------------|
512
+ | `extractCredentials` | `(context) => { username: string; password: string }` | Decodes `Authorization: Basic <base64>` |
513
+ | `verify` | `(opts: { credentials, context }) => Promise<IAuthUser>` | Calls the user-provided `verifyCredentials` |
498
514
 
499
- ### Constructor Behavior
515
+ Constructor throws `500` if `verifyCredentials` is missing from the injected options.
500
516
 
501
- ```typescript
502
- constructor(
503
- @inject({ key: AuthenticateBindingKeys.JWKS_OPTIONS })
504
- protected options: IJWKSVerifierOptions,
505
- ) {
506
- // Calls configurePayloadEncryption({ aesAlgorithm, applicationSecret })
507
- // Remote JWKS is NOT fetched here - fetched lazily via ensureInitialized()
508
- }
509
- ```
517
+ ## Strategy classes
510
518
 
511
- ### Initialization Flow
519
+ All four strategies extend `BaseHelper` and implement `IAuthenticationStrategy<E>`. Each one carries:
512
520
 
513
- The `initialize()` method:
514
- 1. Creates a `createRemoteJWKSet()` from the configured `jwksUrl`
515
- 2. Configures `cacheMaxAge` (default 12h) and `cooldownDuration` (default 30s)
516
- 3. Sets `initialized = true`
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()`.
517
525
 
518
- ### Overridden Methods
526
+ | Strategy | `name` | Injects | File |
527
+ |----------|--------|---------|------|
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) |
530
+ | `JWKSVerifierAuthenticationStrategy` | `Authentication.STRATEGY_JWT` | `JWKSVerifierTokenService` | same file |
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) |
519
532
 
520
- | Method | Behavior |
521
- |--------|----------|
522
- | `doVerify(token)` | Calls `ensureInitialized()`, then `jwtVerify(token, this.jwksVerifier!)`, then `this.decryptPayload()` |
523
- | `getSigner(opts)` | Throws - verifier mode cannot sign tokens |
524
- | `getSigningKey()` | Throws - verifier mode cannot sign tokens |
525
- | `getDefaultTokenExpiresFn()` | Throws - verifier mode has no token expiry |
533
+ > [!NOTE]
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.
526
535
 
527
536
  ## JWKSController
528
537
 
529
- Serves the JWKS endpoint (default path `/certs`). This endpoint is **intentionally unauthenticated** - it serves the public keys needed by external verifiers.
538
+ Serves the JWKS endpoint (default path `/certs`, configurable via `rest.path`). Intentionally unauthenticated - it serves the public keys external verifiers need.
530
539
 
531
- **File:** `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)
532
541
 
533
542
  ```typescript
534
543
  class JWKSController extends BaseRestController {
535
- constructor(
536
- @inject({
537
- key: BindingKeys.build({
538
- namespace: BindingNamespaces.SERVICE,
539
- key: JWKSIssuerTokenService.name,
540
- }),
541
- })
542
- private jwksService: JWKSIssuerTokenService,
543
- ) {
544
+ constructor(@inject(...) private jwksService: JWKSIssuerTokenService) {
544
545
  super({ scope: JWKSController.name, path: '/certs', isStrict: true });
545
546
  }
546
547
 
547
- override binding(): ValueOrPromise<void> {
548
+ override binding() {
548
549
  this.defineRoute({
549
- configs: RouteConfigs.GET_JWKS_CERTS,
550
+ configs: RouteConfigs.GET_JWKS_CERTS, // GET '/'
550
551
  handler: async context => {
551
552
  const jwks = await this.jwksService.getJWKSAsync();
552
553
  context.header('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
@@ -557,107 +558,40 @@ class JWKSController extends BaseRestController {
557
558
  }
558
559
  ```
559
560
 
560
- **Response format:**
561
- ```json
562
- {
563
- "keys": [
564
- {
565
- "kty": "EC",
566
- "kid": "my-key-id-1",
567
- "use": "sig",
568
- "alg": "ES256",
569
- "crv": "P-256",
570
- "x": "...",
571
- "y": "..."
572
- }
573
- ]
574
- }
575
- ```
576
-
577
- **Cache headers:** `Cache-Control: public, max-age=3600, stale-while-revalidate=86400`
578
-
579
- > [!NOTE]
580
- > The `/certs` path is configurable via `rest.path` in `IJWKSIssuerOptions`. The component applies the `@controller` decorator dynamically with the configured path.
581
-
582
- ## BasicTokenService
583
-
584
- All methods are instance methods on <code v-pre>BasicTokenService&lt;E extends Env = Env&gt;</code>, which extends `BaseService`.
561
+ The component applies `@controller({ path })` to `JWKSController` dynamically at binding time (via `Reflect.decorate`), since the path depends on a runtime option.
585
562
 
586
- **File:** `packages/core/src/components/auth/authenticate/services/basic/service.ts`
563
+ ## Controller factory
587
564
 
588
- ### BasicAuthenticationStrategy
565
+ `defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController` builds a `BaseRestController` subclass at runtime.
589
566
 
590
- Extends `BaseHelper` and implements <code v-pre>IAuthenticationStrategy&lt;E&gt;</code>. Generic on <code v-pre>&lt;E extends Env = Env&gt;</code>.
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)
591
568
 
592
- ```typescript
593
- class BasicAuthenticationStrategy<E extends Env = Env>
594
- extends BaseHelper
595
- implements IAuthenticationStrategy<E>
596
- {
597
- name = Authentication.STRATEGY_BASIC;
598
-
599
- constructor(
600
- @inject({
601
- key: BindingKeys.build({
602
- namespace: BindingNamespaces.SERVICE,
603
- key: BasicTokenService.name,
604
- }),
605
- })
606
- private service: BasicTokenService<E>,
607
- ) { ... }
608
-
609
- async authenticate(context: TContext<E, string>): Promise<IAuthUser> {
610
- const credentials = this.service.extractCredentials(context);
611
- return this.service.verify({ credentials, context });
612
- }
613
- }
614
- ```
615
-
616
- ### Methods
617
-
618
- | Method | Signature | Description |
619
- |--------|-----------|-------------|
620
- | `extractCredentials` | <code v-pre>(context: TContext&lt;E, string&gt;) =&gt; { username: string; password: string }</code> | Decodes Base64 <code v-pre>Authorization: Basic &lt;base64&gt;</code> header |
621
- | `verify` | <code v-pre>(opts: { credentials: { username: string; password: string }; context: TContext&lt;E, string&gt; }) =&gt; Promise&lt;IAuthUser&gt;</code> | Calls user-provided `verifyCredentials` callback |
622
-
623
- ### Constructor Behavior
569
+ **How it works:**
624
570
 
625
- ```typescript
626
- constructor(
627
- @inject({ key: AuthenticateBindingKeys.BASIC_OPTIONS })
628
- protected options: TBasicTokenServiceOptions<E>,
629
- ) {
630
- // Throws '[BasicTokenService] Invalid verifyCredentials function' if !options?.verifyCredentials
631
- }
632
- ```
571
+ 1. **Class creation.** `class AuthController extends BaseRestController {}` inside the factory closure, decorated with `@controller({ path: restPath })`, `isStrict: true`.
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.
573
+ 3. **Routes.** Defined in `binding()` via `this.defineRoute()` - see the endpoint table in [Usage & Examples](./usage#api-endpoints).
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.
633
575
 
634
- ## Entity Column Helper Types
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).
635
577
 
636
- The following types are exported for use when extending the auth entity column helpers:
578
+ ## Entity column helper types
637
579
 
638
- ### Permission Types
580
+ Exported for extending the auth entity column helpers - see [Usage & Examples](./usage#entity-column-helpers) for the columns themselves.
639
581
 
640
582
  ```typescript
641
- type TPermissionOptions = {
642
- idType?: 'string' | 'number';
643
- };
644
-
583
+ type TPermissionOptions = { idType?: 'string' | 'number' };
645
584
  type TPermissionCommonColumns = {
646
585
  code: NotNull<PgTextBuilderInitial<...>>;
647
586
  name: NotNull<PgTextBuilderInitial<...>>;
648
587
  subject: NotNull<PgTextBuilderInitial<...>>;
588
+ method: NotNull<PgTextBuilderInitial<...>>;
649
589
  action: NotNull<PgTextBuilderInitial<...>>;
650
590
  scope: NotNull<PgTextBuilderInitial<...>>;
651
- };
652
- ```
653
-
654
- ### Policy Definition Types
655
-
656
- ```typescript
657
- type TPolicyDefinitionOptions = {
658
- idType?: 'string' | 'number';
591
+ description: PgTextBuilderInitial<...>;
659
592
  };
660
593
 
594
+ type TPolicyDefinitionOptions = { idType?: 'string' | 'number' };
661
595
  type TPolicyDefinitionCommonColumns = {
662
596
  variant: ReturnType<typeof text>;
663
597
  subjectType: ReturnType<typeof text>;
@@ -665,92 +599,45 @@ type TPolicyDefinitionCommonColumns = {
665
599
  action: ReturnType<typeof text>;
666
600
  effect: ReturnType<typeof text>;
667
601
  domain: ReturnType<typeof text>;
602
+ metadata: ReturnType<typeof jsonb>;
668
603
  };
669
604
  ```
670
605
 
671
- ## Controller Factory
672
-
673
- The `defineAuthController()` function dynamically creates a controller class at runtime using decorator composition:
674
-
675
- **How it works:**
676
-
677
- 1. **Class creation:** A new class is created dynamically with `class AuthController extends BaseRestController {}` inside the factory closure
678
- 2. **Decorator application:** The `@controller({ path: restPath })` decorator is applied to set the base path. The controller is created with `isStrict: true`
679
- 3. **Service injection:** The auth service is injected via `inject({ key: serviceKey })(AuthController, undefined, 0)` after class definition -- this programmatically applies `@inject` to constructor parameter 0
680
- - Service key is provided via `controllerOpts.serviceKey` (required)
681
- - Service must implement `IAuthService` interface
682
- 4. **Route definition:** Routes are defined in the controller's `binding()` method using `this.defineRoute()`
683
- 5. **Schema customization:** Custom Zod schemas can be provided per endpoint via the `payload` option. Defaults to built-in schemas when not provided, with `AnyObjectSchema` as the response fallback.
684
-
685
- **Factory signature:**
686
-
687
- ```typescript
688
- function defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController;
689
- ```
690
-
691
- > [!NOTE]
692
- > The factory also exports `JWTTokenPayloadSchema`, a Zod schema used for the `/who-am-i` response validation. For `/who-am-i`, it is extended with an optional `userInformation` field that is populated when the request sets `?withUserInformation=true`. The same `getUserInformation` service method backs the dedicated `GET /me` route.
693
-
694
- **Service resolution:**
695
-
696
- The factory applies `@inject` programmatically to constructor parameter 0:
697
-
698
- ```typescript
699
- // Inside defineAuthController, after class definition:
700
- inject({ key: serviceKey })(AuthController, undefined, 0);
701
- ```
702
-
703
- This is equivalent to:
704
- ```typescript
705
- constructor(
706
- @inject({ key: serviceKey })
707
- authService: IAuthService,
708
- ) { ... }
709
- ```
710
-
711
- If the service is not bound, the component will throw: `"[AuthController] Failed to init auth controller | Invalid injectable authentication service!"`
712
-
713
- ## File Structure
606
+ ## File structure
714
607
 
715
608
  ```
716
- packages/core/src/components/auth/authenticate/
717
- ├── common/
718
- │ ├── codecs.ts # AuthenticationFieldCodecs (ROLES_CODEC, build() factory)
719
- │ ├── constants.ts # AuthenticateStrategy, JOSEStandards, JWKSModes, JWKSKeyDrivers, JWKSKeyFormats, Authentication, AuthenticationTokenTypes, AuthenticationModes
720
- │ ├── keys.ts # AuthenticateBindingKeys (REST_OPTIONS, JWT_OPTIONS, JWKS_OPTIONS, BASIC_OPTIONS)
721
- │ ├── types.ts # All option interfaces, discriminated unions, IAuthUser, IJWTTokenPayload, IPayloadFieldCodec, IAuthService
722
- └── index.ts # Barrel export
723
- ├── controllers/
724
- │ ├── factory.ts # defineAuthController() factory + JWTTokenPayloadSchema
725
- └── jwks/
726
- ├── controller.ts # JWKSController (serves /certs endpoint)
727
- └── definitions.ts # Route config for GET /certs
728
- ├── middlewares/
729
- └── authenticate.middleware.ts # Standalone authenticate() convenience function
730
- ├── providers/
731
- └── authentication.provider.ts # AuthenticationProvider (IProvider pattern, creates middleware)
732
- ├── services/
733
- ├── basic/
734
- │ │ └── service.ts # BasicTokenService
735
- │ ├── bearer/
736
- │ │ ├── abstract.service.ts # AbstractBearerTokenService (shared logic)
737
- ├── jws.service.ts # JWSTokenService (symmetric HS256)
738
- └── jwks/
739
- │ │ ├── abstract.service.ts # AbstractJWKSTokenService (lazy-init)
740
- │ ├── issuer.service.ts # JWKSIssuerTokenService
741
- │ │ └── verifier.service.ts # JWKSVerifierTokenService
742
- │ └── index.ts # Barrel export
743
- ├── strategies/
744
- │ ├── basic.strategy.ts # BasicAuthenticationStrategy
745
- │ ├── jws.strategy.ts # JWSAuthenticationStrategy
746
- │ ├── jwks.strategy.ts # JWKSIssuerAuthenticationStrategy + JWKSVerifierAuthenticationStrategy
747
- │ ├── strategy-registry.ts # AuthenticationStrategyRegistry singleton
748
- │ └── index.ts # Barrel export
749
- └── component.ts # AuthenticateComponent
609
+ packages/core-server/src/components/auth/
610
+ ├── authenticate/
611
+ │ ├── common/
612
+ ├── codecs.ts # AuthenticationFieldCodecs (ROLES_CODEC, build() factory)
613
+ ├── constants.ts # AuthenticateStrategy, JOSEStandards, JWKSModes, JWKSKeyDrivers, JWKSKeyFormats, Authentication, AuthenticationTokenTypes, AuthenticationModes
614
+ ├── keys.ts # AuthenticateBindingKeys
615
+ │ ├── types.ts # Option interfaces, discriminated unions, IAuthUser, IJWTTokenPayload, IAuthService
616
+ │ │ └── index.ts
617
+ │ ├── controllers/
618
+ │ ├── factory.ts # defineAuthController() + JWTTokenPayloadSchema
619
+ │ └── jwks/ # JWKSController + route config
620
+ ├── middlewares/
621
+ │ │ └── authenticate.middleware.ts # Standalone authenticate() function
622
+ ├── providers/
623
+ │ │ └── authentication.provider.ts # AuthenticationProvider
624
+ ├── services/
625
+ │ │ ├── basic/service.ts # BasicTokenService
626
+ │ └── bearer/
627
+ │ │ ├── abstract.service.ts # AbstractBearerTokenService
628
+ ├── jws.service.ts # JWSTokenService
629
+ │ │ └── jwks/ # AbstractJWKSTokenService, JWKSIssuerTokenService, JWKSVerifierTokenService
630
+ │ ├── strategies/ # JWSAuthenticationStrategy, JWKS*, BasicAuthenticationStrategy, AuthenticationStrategyRegistry
631
+ │ └── component.ts # AuthenticateComponent
632
+ ├── base/
633
+ └── abstract-auth-registry.ts # AbstractAuthRegistry (shared by authenticate + authorize)
634
+ └── models/
635
+ ├── entities/ # extraUserColumns, extraRoleColumns, extraPermissionColumns, extraPolicyDefinitionColumns
636
+ └── requests/ # SignInRequestSchema, SignUpRequestSchema, ChangePasswordRequestSchema
750
637
  ```
751
638
 
752
- ## See Also
639
+ ## See also
753
640
 
754
- - [Setup & Configuration](./) -- Binding keys, options interfaces, and initial setup
755
- - [Usage & Examples](./usage) -- Securing routes, auth flows, and API endpoints
756
- - [Error Reference](./errors) -- Error messages and troubleshooting
641
+ - [Overview](./) - guided introduction and common tasks
642
+ - [Usage & Examples](./usage) - securing routes, auth flows, JWKS microservice patterns, API endpoints
643
+ - [Error Reference](./errors) - every error message and how to fix it