@venizia/ignis-docs 0.0.6-3 → 0.0.7-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 (113) hide show
  1. package/README.md +125 -388
  2. package/dist/mcp-server/common/config.d.ts +0 -21
  3. package/dist/mcp-server/common/config.d.ts.map +1 -1
  4. package/dist/mcp-server/common/config.js +1 -36
  5. package/dist/mcp-server/common/config.js.map +1 -1
  6. package/dist/mcp-server/helpers/docs.helper.d.ts +0 -24
  7. package/dist/mcp-server/helpers/docs.helper.d.ts.map +1 -1
  8. package/dist/mcp-server/helpers/docs.helper.js +0 -25
  9. package/dist/mcp-server/helpers/docs.helper.js.map +1 -1
  10. package/dist/mcp-server/helpers/github.helper.d.ts +0 -13
  11. package/dist/mcp-server/helpers/github.helper.d.ts.map +1 -1
  12. package/dist/mcp-server/helpers/github.helper.js +3 -20
  13. package/dist/mcp-server/helpers/github.helper.js.map +1 -1
  14. package/dist/mcp-server/index.js +0 -20
  15. package/dist/mcp-server/index.js.map +1 -1
  16. package/dist/mcp-server/tools/base.tool.d.ts +2 -79
  17. package/dist/mcp-server/tools/base.tool.d.ts.map +1 -1
  18. package/dist/mcp-server/tools/base.tool.js +1 -38
  19. package/dist/mcp-server/tools/base.tool.js.map +1 -1
  20. package/dist/mcp-server/tools/docs/get-document-content.tool.d.ts.map +1 -1
  21. package/dist/mcp-server/tools/docs/get-document-content.tool.js +0 -9
  22. package/dist/mcp-server/tools/docs/get-document-content.tool.js.map +1 -1
  23. package/dist/mcp-server/tools/docs/get-document-metadata.tool.d.ts.map +1 -1
  24. package/dist/mcp-server/tools/docs/get-document-metadata.tool.js +0 -9
  25. package/dist/mcp-server/tools/docs/get-document-metadata.tool.js.map +1 -1
  26. package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts +0 -6
  27. package/dist/mcp-server/tools/docs/get-package-overview.tool.d.ts.map +1 -1
  28. package/dist/mcp-server/tools/docs/get-package-overview.tool.js +1 -24
  29. package/dist/mcp-server/tools/docs/get-package-overview.tool.js.map +1 -1
  30. package/dist/mcp-server/tools/docs/list-categories.tool.d.ts.map +1 -1
  31. package/dist/mcp-server/tools/docs/list-categories.tool.js +0 -9
  32. package/dist/mcp-server/tools/docs/list-categories.tool.js.map +1 -1
  33. package/dist/mcp-server/tools/docs/list-documents.tool.d.ts.map +1 -1
  34. package/dist/mcp-server/tools/docs/list-documents.tool.js +0 -9
  35. package/dist/mcp-server/tools/docs/list-documents.tool.js.map +1 -1
  36. package/dist/mcp-server/tools/docs/search-documents.tool.d.ts.map +1 -1
  37. package/dist/mcp-server/tools/docs/search-documents.tool.js +0 -9
  38. package/dist/mcp-server/tools/docs/search-documents.tool.js.map +1 -1
  39. package/dist/mcp-server/tools/github/list-project-files.tool.d.ts.map +1 -1
  40. package/dist/mcp-server/tools/github/list-project-files.tool.js +0 -9
  41. package/dist/mcp-server/tools/github/list-project-files.tool.js.map +1 -1
  42. package/dist/mcp-server/tools/github/search-code.tool.d.ts.map +1 -1
  43. package/dist/mcp-server/tools/github/search-code.tool.js +1 -13
  44. package/dist/mcp-server/tools/github/search-code.tool.js.map +1 -1
  45. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts +0 -4
  46. package/dist/mcp-server/tools/github/verify-dependencies.tool.d.ts.map +1 -1
  47. package/dist/mcp-server/tools/github/verify-dependencies.tool.js +1 -18
  48. package/dist/mcp-server/tools/github/verify-dependencies.tool.js.map +1 -1
  49. package/dist/mcp-server/tools/github/view-source-file.tool.d.ts.map +1 -1
  50. package/dist/mcp-server/tools/github/view-source-file.tool.js +0 -9
  51. package/dist/mcp-server/tools/github/view-source-file.tool.js.map +1 -1
  52. package/dist/mcp-server/tools/index.d.ts.map +1 -1
  53. package/dist/mcp-server/tools/index.js +0 -2
  54. package/dist/mcp-server/tools/index.js.map +1 -1
  55. package/package.json +1 -1
  56. package/wiki/best-practices/api-usage-examples.md +7 -5
  57. package/wiki/best-practices/code-style-standards/advanced-patterns.md +1 -1
  58. package/wiki/best-practices/code-style-standards/constants-configuration.md +1 -1
  59. package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
  60. package/wiki/best-practices/code-style-standards/function-patterns.md +1 -1
  61. package/wiki/best-practices/common-pitfalls.md +1 -1
  62. package/wiki/best-practices/data-modeling.md +33 -1
  63. package/wiki/best-practices/error-handling.md +7 -4
  64. package/wiki/best-practices/performance-optimization.md +1 -1
  65. package/wiki/best-practices/security-guidelines.md +5 -4
  66. package/wiki/guides/core-concepts/components-guide.md +1 -1
  67. package/wiki/guides/core-concepts/controllers.md +14 -8
  68. package/wiki/guides/core-concepts/persistent/models.md +32 -0
  69. package/wiki/guides/core-concepts/services.md +2 -1
  70. package/wiki/guides/get-started/5-minute-quickstart.md +1 -1
  71. package/wiki/guides/tutorials/building-a-crud-api.md +2 -1
  72. package/wiki/guides/tutorials/complete-installation.md +2 -2
  73. package/wiki/guides/tutorials/ecommerce-api.md +3 -3
  74. package/wiki/guides/tutorials/realtime-chat.md +7 -6
  75. package/wiki/index.md +2 -1
  76. package/wiki/references/base/application.md +28 -0
  77. package/wiki/references/base/components.md +2 -1
  78. package/wiki/references/base/controllers.md +31 -4
  79. package/wiki/references/base/datasources.md +6 -2
  80. package/wiki/references/base/dependency-injection.md +31 -0
  81. package/wiki/references/base/filter-system/fields-order-pagination.md +8 -1
  82. package/wiki/references/base/middlewares.md +2 -1
  83. package/wiki/references/base/models.md +144 -2
  84. package/wiki/references/base/repositories/advanced.md +2 -2
  85. package/wiki/references/base/repositories/index.md +24 -1
  86. package/wiki/references/base/repositories/soft-deletable.md +213 -0
  87. package/wiki/references/base/services.md +2 -1
  88. package/wiki/references/components/authentication/api.md +525 -205
  89. package/wiki/references/components/authentication/errors.md +502 -105
  90. package/wiki/references/components/authentication/index.md +388 -75
  91. package/wiki/references/components/authentication/usage.md +575 -247
  92. package/wiki/references/components/authorization/usage.md +62 -0
  93. package/wiki/references/components/health-check.md +2 -1
  94. package/wiki/references/components/socket-io/index.md +9 -4
  95. package/wiki/references/components/socket-io/usage.md +1 -1
  96. package/wiki/references/components/static-asset/index.md +3 -5
  97. package/wiki/references/components/swagger.md +2 -1
  98. package/wiki/references/configuration/environment-variables.md +2 -1
  99. package/wiki/references/configuration/index.md +40 -1
  100. package/wiki/references/helpers/error/index.md +1 -1
  101. package/wiki/references/helpers/inversion/index.md +1 -1
  102. package/wiki/references/helpers/redis/index.md +2 -9
  103. package/wiki/references/quick-reference.md +3 -5
  104. package/wiki/references/utilities/crypto.md +2 -2
  105. package/wiki/references/utilities/date.md +5 -5
  106. package/wiki/references/utilities/index.md +3 -11
  107. package/wiki/references/utilities/jsx.md +4 -2
  108. package/wiki/references/utilities/module.md +1 -1
  109. package/wiki/references/utilities/parse.md +24 -4
  110. package/wiki/references/utilities/performance.md +2 -2
  111. package/wiki/references/utilities/promise.md +4 -4
  112. package/wiki/references/utilities/request.md +2 -2
  113. package/wiki/references/utilities/schema.md +17 -8
@@ -1,91 +1,122 @@
1
1
  # Authentication -- API Reference
2
2
 
3
- > Architecture, service internals, strategy registry, and controller factory. See [Setup & Configuration](./) for initial setup.
3
+ > Architecture, service hierarchy, strategy registry, JWKS controller, and controller factory. See [Setup & Configuration](./) for initial setup.
4
4
 
5
5
  ## Architecture
6
6
 
7
7
  ```
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
+ ▼ └──────────────────┘
8
49
  ┌──────────────────────────────────────────────────────────┐
9
- Application
50
+ Bearer Token Service Hierarchy
10
51
  │ │
11
- preConfigure()
12
- │ ├── bind JWT_OPTIONS / BASIC_OPTIONS / REST_OPTIONS
13
- │ ├── this.component(AuthenticateComponent)
14
- └── AuthenticationStrategyRegistry.register()
15
- └──────────────────────┬───────────────────────────────────┘
16
-
17
-
18
- ┌──────────────────────────────────────────────────────────┐
19
- AuthenticateComponent.binding()
20
-
21
- 1. validateOptions() at least one auth required
22
- 2. defineJWTAuth() bind JWTTokenService
23
- 3. defineBasicAuth() — bind BasicTokenService
24
- 4. defineControllers() — defineAuthController() │
25
- 5. defineOAuth2() — stub (not yet implemented)
26
- └──────────────────────┬───────────────────────────────────┘
27
-
28
- ┌─────────────┼─────────────┐
29
- ▼ ▼ ▼
30
- ┌────────────┐ ┌──────────────┐ ┌──────────────────┐
31
- JWTToken │ BasicToken │ │ AuthController │
32
- Service │ Service │ │ (factory-built)
33
- └──────┬─────┘ └──────┬───────┘ └────────┬─────────┘
34
- │ │ │
35
- ▼ ▼ ▼
36
- ┌────────────┐ ┌──────────────┐ ┌──────────────────┐
37
- │ JWT │ │ Basic │ │ /sign-in │
38
- │ Strategy │ │ Strategy │ │ /sign-up │
39
- │ │ │ │ │ /change-password │
40
- └────────────┘ └──────────────┘ │ /who-am-i │
41
- └──────────────────┘
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
+ └──────────────────────────────────────────────────────────┘
42
75
  ```
43
76
 
44
77
  ### Tech Stack
45
78
 
46
79
  | Technology | Purpose |
47
80
  |------------|---------|
48
- | **`jose`** | JWT signing (`SignJWT`), verification (`jwtVerify`), and type definitions (`JWTPayload`, `JWTVerifyResult`) |
49
- | **`@venizia/ignis-helpers`** | `AES` utility for payload field encryption, `BaseHelper`/`BaseService` base classes, `getError` for error creation, `HTTP` result codes |
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 |
50
83
  | **Hono middleware** | Route-level authentication integration via `createMiddleware` from `hono/factory` |
84
+ | **`node:fs/promises`** | Async file reading for JWKS key files |
51
85
  | **Drizzle ORM** | Database access for user lookup (in your implementation) |
52
- | **lodash/isEmpty** | Used in strategy registry for name validation |
53
86
 
54
- ## Component Private Methods
87
+ ## Component Methods
55
88
 
56
- The `AuthenticateComponent` uses four private methods during its `binding()` lifecycle:
89
+ The `AuthenticateComponent` uses five methods during its `binding()` lifecycle (four private, one public):
57
90
 
58
91
  | Method | Purpose |
59
92
  |--------|---------|
60
- | `validateOptions(opts)` | Validates that at least one of `jwtOptions` or `basicOptions` is present. Throws if neither is provided. |
61
- | `defineJWTAuth(opts)` | Validates JWT secrets (rejects falsy values and `'unknown_secret'`), validates `getTokenExpiresFn`, binds `JWTTokenService` as a service. Logs debug if skipped. |
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`. |
62
95
  | `defineBasicAuth(opts)` | Validates `verifyCredentials` callback presence, binds `BasicTokenService` as a service. Logs debug if skipped. |
63
96
  | `defineControllers(opts)` | Requires `jwtOptions` when `useAuthController: true`. Calls `defineAuthController()` factory and registers the generated controller. |
64
- | `defineOAuth2()` | Stub method -- not yet implemented. Called during `binding()` but performs no action. |
97
+ | `defineOAuth2()` | **Public** stub method -- not yet implemented. Called during `binding()` but performs no action. |
65
98
 
66
- > [!WARNING]
67
- > **Security concern:** The `defineJWTAuth()` method includes the actual secret value in its error message when validation fails (e.g., <code v-pre>[defineJWTAuth] Invalid jwtSecret | Provided: {{jwtSecret}}</code>). Ensure these startup errors are never exposed to end users or logged in production without sanitization.
99
+ > [!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`.
68
101
 
69
102
  ## Strategy Registry
70
103
 
71
- <code v-pre>AuthenticationStrategyRegistry&lt;E extends Env = Env&gt;</code> is a **singleton** that manages all registered strategies. It extends `BaseHelper`.
104
+ `AuthenticationStrategyRegistry` is a **singleton** that manages all registered strategies. It extends `AbstractAuthRegistry<IAuthenticationStrategy>` (not generic on `Env`).
72
105
 
73
106
  ### API
74
107
 
75
108
  | Method | Signature | Returns | Description |
76
109
  |--------|-----------|---------|-------------|
77
110
  | `getInstance()` | `static` | `AuthenticationStrategyRegistry` | Returns the singleton instance (creates if not exists) |
78
- | `getStrategyKey` | `(opts: { name: string }) => string` | `string` | Returns the binding key for a strategy: <code v-pre>authentication.strategy.{{name}}</code> |
79
- | `getStrategy` | `(opts: { container: Container; name: string }) => IAuthenticationStrategy` | `IAuthenticationStrategy` | Resolves a strategy instance from the container by name |
80
- | `register` | <code v-pre>(opts: { container: Container; strategies: Array&lt;{ name: string; strategy: TClass&lt;IAuthenticationStrategy&lt;E&gt;&gt; }&gt; }) =&gt; this</code> | `this` | Registers strategies as singletons in the container. Returns `this` for chaining. |
81
- | `authenticate` | `(opts: { strategies: string[]; mode?: TAuthMode }) => MiddlewareHandler` | `MiddlewareHandler` | Creates a Hono middleware that performs the auth check |
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 |
82
113
 
83
114
  **Registration:**
84
115
  ```typescript
85
116
  AuthenticationStrategyRegistry.getInstance().register({
86
117
  container: this,
87
118
  strategies: [
88
- { name: Authentication.STRATEGY_JWT, strategy: JWTAuthenticationStrategy },
119
+ { name: Authentication.STRATEGY_JWT, strategy: JWKSIssuerAuthenticationStrategy },
89
120
  { name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
90
121
  ],
91
122
  });
@@ -95,60 +126,181 @@ AuthenticationStrategyRegistry.getInstance().register({
95
126
  > `register()` returns `this`, enabling method chaining if needed.
96
127
 
97
128
  **How it works:**
98
- - Strategies are stored in an internal <code v-pre>Map&lt;string, { container, strategyClass }&gt;</code> and also bound to the DI container as singletons
129
+ - Strategies are stored in an internal map and bound to the DI container as singletons
99
130
  - Binding keys follow the pattern `authentication.strategy.{name}` (e.g., `authentication.strategy.jwt`, `authentication.strategy.basic`)
100
- - The `authenticate()` method returns a Hono `MiddlewareHandler` that performs the auth check
101
- - The standalone `authenticate()` function is a convenience wrapper around the registry singleton
102
-
103
- **Strategy binding:**
104
- ```typescript
105
- // Internally, the registry binds strategies like this:
106
- container.bind({ key: 'authentication.strategy.jwt' })
107
- .toClass(JWTAuthenticationStrategy)
108
- .setScope(BindingScopes.SINGLETON);
109
- ```
131
+ - The standalone `authenticate()` function creates middleware via `AuthenticationProvider`, which uses the registry to resolve strategies
110
132
 
111
133
  **Middleware creation:**
112
134
 
113
135
  The `authenticate()` function returns a Hono middleware that:
114
- 1. Checks if `Authentication.SKIP_AUTHENTICATION` is set on context -- if true, skips entirely (logs debug)
115
- 2. Checks if `Authentication.CURRENT_USER` is already set on context -- if true, skips (already authenticated)
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)
116
138
  3. Reads `strategies` and `mode` from the provided options
117
139
  4. Executes strategies based on mode (`any` or `all`)
118
140
  5. On success, sets `Authentication.CURRENT_USER` and `Authentication.AUDIT_USER_ID` on context
119
141
  6. On failure, throws 401 with list of tried strategies
120
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
+
121
168
  ### Standalone `authenticate()` Function
122
169
 
123
170
  ```typescript
171
+ const authenticationProvider = new AuthenticationProvider();
172
+ const authenticateFn = authenticationProvider.value();
173
+
124
174
  export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) => {
125
- return AuthenticationStrategyRegistry.getInstance().authenticate(opts);
175
+ return authenticateFn(opts);
126
176
  };
127
177
  ```
128
178
 
129
- This is the primary export for creating auth middleware. It delegates directly to the singleton registry.
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.
130
180
 
131
181
  > [!NOTE]
132
- > In `all` mode, if every strategy passes but the final user payload has no `userId`, the middleware throws a `401` with message `"Failed to identify authenticated user!"`. The `any` mode collects errors from each failing strategy and only throws after all strategies are exhausted.
182
+ > In `all` mode, if every strategy passes but the final 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
234
+ ```
133
235
 
134
- ## JWTTokenService
236
+ ## AbstractBearerTokenService
135
237
 
136
- All methods are instance methods on <code v-pre>JWTTokenService&lt;E extends Env = Env&gt;</code>, which extends `BaseService`.
238
+ Base class for all Bearer token services. Extends `BaseService`. Generic on <code v-pre>&lt;E extends Env = Env&gt;</code>.
137
239
 
138
- ### JWTAuthenticationStrategy
240
+ **File:** `packages/core/src/components/auth/authenticate/services/bearer/abstract.service.ts`
139
241
 
140
- Extends `BaseHelper` and implements <code v-pre>IAuthenticationStrategy&lt;E&gt;</code>. Generic on <code v-pre>&lt;E extends Env = Env&gt;</code>.
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
249
+
250
+ | Field | Type | Default | Description |
251
+ |-------|------|---------|-------------|
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)
270
+
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 |
277
+
278
+ ## JWSTokenService
279
+
280
+ Symmetric JWT (HS256) token service with optional AES-encrypted payloads. Extends `AbstractBearerTokenService`.
281
+
282
+ **File:** `packages/core/src/components/auth/authenticate/services/bearer/jws.service.ts`
283
+
284
+ ### JWSAuthenticationStrategy
285
+
286
+ Extends `BaseHelper` and implements <code v-pre>IAuthenticationStrategy&lt;E&gt;</code>.
141
287
 
142
288
  ```typescript
143
- class JWTAuthenticationStrategy<E extends Env = Env>
289
+ class JWSAuthenticationStrategy<E extends Env = Env>
144
290
  extends BaseHelper
145
291
  implements IAuthenticationStrategy<E>
146
292
  {
147
- name = Authentication.STRATEGY_JWT; // 'jwt'
293
+ name = Authentication.STRATEGY_JWT;
294
+ standard = JOSEStandards.JWS;
148
295
 
149
296
  constructor(
150
- @inject({ key: 'services.JWTTokenService' })
151
- private service: JWTTokenService<E>,
297
+ @inject({
298
+ key: BindingKeys.build({
299
+ namespace: BindingNamespaces.SERVICE,
300
+ key: JWSTokenService.name,
301
+ }),
302
+ })
303
+ private service: JWSTokenService<E>,
152
304
  ) { ... }
153
305
 
154
306
  authenticate(context: TContext<E, string>): Promise<IAuthUser> {
@@ -158,48 +310,281 @@ class JWTAuthenticationStrategy<E extends Env = Env>
158
310
  }
159
311
  ```
160
312
 
313
+ ### Protected Fields
314
+
315
+ | Field | Type | Description |
316
+ |-------|------|-------------|
317
+ | `jwtSecret` | `Uint8Array` | Encoded JWT secret for `jose` signing/verification |
318
+ | `options` | `IJWSTokenServiceOptions` | Injected options |
319
+
320
+ ### Constructor Behavior
321
+
322
+ The constructor validates required options and throws immediately (status 500) if any are missing:
323
+
324
+ ```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
333
+ }
334
+ ```
335
+
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.
338
+
339
+ ### Overridden Methods
340
+
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` |
347
+
348
+ ## AbstractJWKSTokenService
349
+
350
+ Base class for JWKS token services (Issuer + Verifier). Extends `AbstractBearerTokenService`.
351
+
352
+ **File:** `packages/core/src/components/auth/authenticate/services/bearer/jwks/abstract.service.ts`
353
+
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.
355
+
356
+ ### Protected Fields
357
+
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) |
362
+
161
363
  ### Methods
162
364
 
163
365
  | Method | Signature | Description |
164
366
  |--------|-----------|-------------|
165
- | `extractCredentials` | <code v-pre>(context: TContext&lt;E, string&gt;) =&gt; { type: string; token: string }</code> | Extracts Bearer token from Authorization header |
166
- | `verify` | <code v-pre>(opts: { type: string; token: string }) =&gt; Promise&lt;IJWTTokenPayload&gt;</code> | Verifies JWT signature via `jose.jwtVerify()` and decrypts payload |
167
- | `generate` | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn?: TGetTokenExpiresFn }) =&gt; Promise&lt;string&gt;</code> | Encrypts payload, signs JWT with configurable expiration |
168
- | `getSigner` | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn: TGetTokenExpiresFn }) =&gt; Promise&lt;SignJWT&gt;</code> | Creates a `jose.SignJWT` instance with encrypted payload, iat, exp, nbf |
169
- | `encryptPayload` | <code v-pre>(payload: IJWTTokenPayload) =&gt; Record&lt;string, string&gt;</code> | AES-encrypts non-standard JWT fields (keys + values) |
170
- | `decryptPayload` | <code v-pre>(opts: { result: JWTVerifyResult&lt;IJWTTokenPayload&gt; }) =&gt; IJWTTokenPayload</code> | Decrypts AES-encrypted fields back to IJWTTokenPayload |
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. |
171
368
 
172
- ### Static Fields
369
+ ### Abstract Methods
370
+
371
+ | Method | Signature | Description |
372
+ |--------|-----------|-------------|
373
+ | `initialize` | `() => Promise<void>` | Perform one-time async initialization (load keys, create verifier, etc.) |
374
+
375
+ ## JWKSIssuerTokenService
376
+
377
+ Asymmetric JWT token service (issuer mode). Extends `AbstractJWKSTokenService`.
378
+
379
+ **File:** `packages/core/src/components/auth/authenticate/services/bearer/jwks/issuer.service.ts`
380
+
381
+ ### JWKSIssuerAuthenticationStrategy
173
382
 
174
- - `JWT_COMMON_FIELDS`: <code v-pre>Set&lt;'iss' | 'sub' | 'aud' | 'jti' | 'nbf' | 'exp' | 'iat'&gt;</code> -- fields preserved as-is during encryption
383
+ ```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
+ }
406
+ ```
175
407
 
176
408
  ### Protected Fields
177
409
 
178
- - `aes`: `AES` -- AES utility instance initialized with the configured algorithm
179
- - `jwtSecret`: `Uint8Array` -- encoded JWT secret for `jose` signing/verification
180
- - `options`: `IJWTTokenServiceOptions` -- injected options
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 |
181
415
 
182
416
  ### Constructor Behavior
183
417
 
184
- The constructor validates all three required options and throws immediately (status 500) if any are missing:
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
+ }
426
+ ```
427
+
428
+ ### Initialization Flow
429
+
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`**
436
+
437
+ ### Overridden Methods
438
+
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` |
445
+
446
+ ### JWKS Methods
447
+
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. |
452
+
453
+ ### Internal Methods
454
+
455
+ | Method | Signature | Description |
456
+ |--------|-----------|-------------|
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) |
459
+
460
+ ## JWKSVerifierTokenService
461
+
462
+ Asymmetric JWT token service (verifier mode). Extends `AbstractJWKSTokenService`.
463
+
464
+ **File:** `packages/core/src/components/auth/authenticate/services/bearer/jwks/verifier.service.ts`
465
+
466
+ ### JWKSVerifierAuthenticationStrategy
467
+
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
+ ```
492
+
493
+ ### Protected Fields
494
+
495
+ | Field | Type | Default | Description |
496
+ |-------|------|---------|-------------|
497
+ | `jwksVerifier` | `ReturnType<typeof createRemoteJWKSet> \| null` | `null` | Remote JWKS verifier function |
498
+
499
+ ### Constructor Behavior
185
500
 
186
501
  ```typescript
187
502
  constructor(
188
- @inject({ key: AuthenticateBindingKeys.JWT_OPTIONS })
189
- protected options: IJWTTokenServiceOptions,
503
+ @inject({ key: AuthenticateBindingKeys.JWKS_OPTIONS })
504
+ protected options: IJWKSVerifierOptions,
190
505
  ) {
191
- // Throws '[JWTTokenService] Invalid jwtSecret' if !jwtSecret
192
- // Throws '[JWTTokenService] Invalid applicationSecret' if !applicationSecret
193
- // Throws '[JWTTokenService] Invalid getTokenExpiresFn' if !getTokenExpiresFn
194
- // Initializes AES with configured algorithm (default 'aes-256-cbc')
195
- // Encodes jwtSecret to Uint8Array for jose
506
+ // Calls configurePayloadEncryption({ aesAlgorithm, applicationSecret })
507
+ // Remote JWKS is NOT fetched here — fetched lazily via ensureInitialized()
508
+ }
509
+ ```
510
+
511
+ ### Initialization Flow
512
+
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`
517
+
518
+ ### Overridden Methods
519
+
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 |
526
+
527
+ ## JWKSController
528
+
529
+ Serves the JWKS endpoint (default path `/certs`). This endpoint is **intentionally unauthenticated** — it serves the public keys needed by external verifiers.
530
+
531
+ **File:** `packages/core/src/components/auth/authenticate/controllers/jwks/controller.ts`
532
+
533
+ ```typescript
534
+ class JWKSController extends BaseController {
535
+ constructor(
536
+ @inject({
537
+ key: BindingKeys.build({
538
+ namespace: BindingNamespaces.SERVICE,
539
+ key: JWKSIssuerTokenService.name,
540
+ }),
541
+ })
542
+ private jwksService: JWKSIssuerTokenService,
543
+ ) {
544
+ super({ scope: JWKSController.name, path: '/certs', isStrict: true });
545
+ }
546
+
547
+ override binding(): ValueOrPromise<void> {
548
+ this.defineRoute({
549
+ configs: RouteConfigs.GET_JWKS_CERTS,
550
+ handler: async context => {
551
+ const jwks = await this.jwksService.getJWKSAsync();
552
+ context.header('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
553
+ return context.json(jwks, HTTP.ResultCodes.RS_2.Ok);
554
+ },
555
+ });
556
+ }
557
+ }
558
+ ```
559
+
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
+ ]
196
574
  }
197
575
  ```
198
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
+
199
582
  ## BasicTokenService
200
583
 
201
584
  All methods are instance methods on <code v-pre>BasicTokenService&lt;E extends Env = Env&gt;</code>, which extends `BaseService`.
202
585
 
586
+ **File:** `packages/core/src/components/auth/authenticate/services/basic/service.ts`
587
+
203
588
  ### BasicAuthenticationStrategy
204
589
 
205
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>.
@@ -209,10 +594,15 @@ class BasicAuthenticationStrategy<E extends Env = Env>
209
594
  extends BaseHelper
210
595
  implements IAuthenticationStrategy<E>
211
596
  {
212
- name = Authentication.STRATEGY_BASIC; // 'basic'
597
+ name = Authentication.STRATEGY_BASIC;
213
598
 
214
599
  constructor(
215
- @inject({ key: 'services.BasicTokenService' })
600
+ @inject({
601
+ key: BindingKeys.build({
602
+ namespace: BindingNamespaces.SERVICE,
603
+ key: BasicTokenService.name,
604
+ }),
605
+ })
216
606
  private service: BasicTokenService<E>,
217
607
  ) { ... }
218
608
 
@@ -230,16 +620,12 @@ class BasicAuthenticationStrategy<E extends Env = Env>
230
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 |
231
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 |
232
622
 
233
- ### Private Fields
234
-
235
- - `verifyCredentials` -- the callback function extracted from injected options
236
-
237
623
  ### Constructor Behavior
238
624
 
239
625
  ```typescript
240
626
  constructor(
241
627
  @inject({ key: AuthenticateBindingKeys.BASIC_OPTIONS })
242
- protected options: IBasicTokenServiceOptions<E>,
628
+ protected options: TBasicTokenServiceOptions<E>,
243
629
  ) {
244
630
  // Throws '[BasicTokenService] Invalid verifyCredentials function' if !options?.verifyCredentials
245
631
  }
@@ -299,7 +685,7 @@ The `defineAuthController()` function dynamically creates a controller class at
299
685
  1. **Class creation:** A new class is created dynamically with `class AuthController extends BaseController {}` inside the factory closure
300
686
  2. **Decorator application:** The `@controller({ path: restPath })` decorator is applied to set the base path. The controller is created with `isStrict: true`
301
687
  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
302
- - Default service key: `'services.AuthenticationService'`
688
+ - Service key is provided via `controllerOpts.serviceKey` (required)
303
689
  - Service must implement `IAuthService` interface
304
690
  4. **Route definition:** Routes are defined in the controller's `binding()` method using `this.defineRoute()`
305
691
  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.
@@ -313,111 +699,6 @@ function defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthContr
313
699
  > [!NOTE]
314
700
  > The factory also exports `JWTTokenPayloadSchema`, a Zod schema used for the `/who-am-i` response validation.
315
701
 
316
- **Internal route binding:**
317
-
318
- ```typescript
319
- // Inside the factory-generated controller
320
- binding(): void {
321
- // /sign-in -- no auth, delegates to service.signIn()
322
- this.defineRoute({
323
- configs: {
324
- path: '/sign-in',
325
- method: 'post',
326
- request: {
327
- body: jsonContent({
328
- description: 'Sign-in request body',
329
- required: true,
330
- schema: payload?.signIn?.request?.schema ?? SignInRequestSchema,
331
- }),
332
- },
333
- responses: jsonResponse({
334
- schema: payload?.signIn?.request?.schema ?? AnyObjectSchema,
335
- description: 'Success Response',
336
- }),
337
- },
338
- handler: async (context) => {
339
- const body = await context.req.json();
340
- const rs = await this.service.signIn(context, body);
341
- return context.json(rs, HTTP.ResultCodes.RS_2.Ok);
342
- },
343
- });
344
-
345
- // /sign-up -- conditionally requires JWT auth
346
- this.defineRoute({
347
- configs: {
348
- path: '/sign-up',
349
- method: 'post',
350
- authenticate: {
351
- strategies: !requireAuthenticatedSignUp ? [] : [Authentication.STRATEGY_JWT],
352
- },
353
- request: {
354
- body: jsonContent({
355
- description: 'Sign-up request body',
356
- required: true,
357
- schema: payload?.signUp?.request?.schema ?? SignUpRequestSchema,
358
- }),
359
- },
360
- responses: jsonResponse({
361
- schema: payload?.signUp?.response?.schema ?? AnyObjectSchema,
362
- description: 'Success Response',
363
- }),
364
- },
365
- handler: async (context) => {
366
- const body = await context.req.json();
367
- const rs = await this.service.signUp(context, body);
368
- return context.json(rs, HTTP.ResultCodes.RS_2.Ok);
369
- },
370
- });
371
-
372
- // /change-password -- always requires JWT auth
373
- this.defineRoute({
374
- configs: {
375
- path: '/change-password',
376
- method: 'post',
377
- authenticate: { strategies: [Authentication.STRATEGY_JWT] },
378
- request: {
379
- body: jsonContent({
380
- description: 'Change password request body',
381
- required: true,
382
- schema: payload?.changePassword?.request?.schema ?? ChangePasswordRequestSchema,
383
- }),
384
- },
385
- responses: jsonResponse({
386
- schema: payload?.changePassword?.response?.schema ?? AnyObjectSchema,
387
- description: 'Success Response',
388
- }),
389
- },
390
- handler: async (context) => {
391
- const body = await context.req.json();
392
- const rs = await this.service.changePassword(context, body);
393
- return context.json(rs, HTTP.ResultCodes.RS_2.Ok);
394
- },
395
- });
396
-
397
- // /who-am-i -- always requires JWT, returns current user from context
398
- this.defineRoute({
399
- configs: {
400
- path: '/who-am-i',
401
- method: 'get',
402
- authenticate: { strategies: [Authentication.STRATEGY_JWT] },
403
- responses: {
404
- [HTTP.ResultCodes.RS_2.Ok]: jsonContent({
405
- description: 'Success Response',
406
- schema: JWTTokenPayloadSchema,
407
- }),
408
- },
409
- },
410
- handler: (context) => {
411
- const currentUser = context.get(Authentication.CURRENT_USER as never) as IJWTTokenPayload;
412
- return context.json(currentUser, HTTP.ResultCodes.RS_2.Ok);
413
- },
414
- });
415
- }
416
- ```
417
-
418
- > [!TIP]
419
- > If the default request/response schemas do not fit your needs, provide custom Zod schemas through the `payload` option in `controllerOpts`. This allows full control over validation while keeping the built-in routing.
420
-
421
702
  **Service resolution:**
422
703
 
423
704
  The factory applies `@inject` programmatically to constructor parameter 0:
@@ -437,6 +718,45 @@ constructor(
437
718
 
438
719
  If the service is not bound, the component will throw: `"[AuthController] Failed to init auth controller | Invalid injectable authentication service!"`
439
720
 
721
+ ## File Structure
722
+
723
+ ```
724
+ packages/core/src/components/auth/authenticate/
725
+ ├── common/
726
+ │ ├── codecs.ts # AuthenticationFieldCodecs (ROLES_CODEC, build() factory)
727
+ │ ├── constants.ts # AuthenticateStrategy, JOSEStandards, JWKSModes, JWKSKeyDrivers, JWKSKeyFormats, Authentication, AuthenticationTokenTypes, AuthenticationModes
728
+ │ ├── keys.ts # AuthenticateBindingKeys (REST_OPTIONS, JWT_OPTIONS, JWKS_OPTIONS, BASIC_OPTIONS)
729
+ │ ├── types.ts # All option interfaces, discriminated unions, IAuthUser, IJWTTokenPayload, IPayloadFieldCodec, IAuthService
730
+ │ └── index.ts # Barrel export
731
+ ├── controllers/
732
+ │ ├── factory.ts # defineAuthController() factory + JWTTokenPayloadSchema
733
+ │ └── jwks/
734
+ │ ├── controller.ts # JWKSController (serves /certs endpoint)
735
+ │ └── definitions.ts # Route config for GET /certs
736
+ ├── middlewares/
737
+ │ └── authenticate.middleware.ts # Standalone authenticate() convenience function
738
+ ├── providers/
739
+ │ └── authentication.provider.ts # AuthenticationProvider (IProvider pattern, creates middleware)
740
+ ├── services/
741
+ │ ├── basic/
742
+ │ │ └── service.ts # BasicTokenService
743
+ │ ├── bearer/
744
+ │ │ ├── abstract.service.ts # AbstractBearerTokenService (shared logic)
745
+ │ │ ├── jws.service.ts # JWSTokenService (symmetric HS256)
746
+ │ │ └── jwks/
747
+ │ │ ├── abstract.service.ts # AbstractJWKSTokenService (lazy-init)
748
+ │ │ ├── issuer.service.ts # JWKSIssuerTokenService
749
+ │ │ └── verifier.service.ts # JWKSVerifierTokenService
750
+ │ └── index.ts # Barrel export
751
+ ├── strategies/
752
+ │ ├── basic.strategy.ts # BasicAuthenticationStrategy
753
+ │ ├── jws.strategy.ts # JWSAuthenticationStrategy
754
+ │ ├── jwks.strategy.ts # JWKSIssuerAuthenticationStrategy + JWKSVerifierAuthenticationStrategy
755
+ │ ├── strategy-registry.ts # AuthenticationStrategyRegistry singleton
756
+ │ └── index.ts # Barrel export
757
+ └── component.ts # AuthenticateComponent
758
+ ```
759
+
440
760
  ## See Also
441
761
 
442
762
  - [Setup & Configuration](./) -- Binding keys, options interfaces, and initial setup