@venizia/ignis-docs 0.0.5 → 0.0.6-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/wiki/best-practices/architecture-decisions.md +0 -8
- package/wiki/best-practices/code-style-standards/control-flow.md +1 -1
- package/wiki/best-practices/performance-optimization.md +3 -3
- package/wiki/best-practices/security-guidelines.md +2 -2
- package/wiki/best-practices/troubleshooting-tips.md +1 -1
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/components.md +2 -2
- package/wiki/guides/core-concepts/dependency-injection.md +1 -1
- package/wiki/guides/core-concepts/services.md +1 -1
- package/wiki/guides/tutorials/building-a-crud-api.md +1 -1
- package/wiki/guides/tutorials/ecommerce-api.md +2 -2
- package/wiki/guides/tutorials/realtime-chat.md +6 -6
- package/wiki/guides/tutorials/testing.md +1 -1
- package/wiki/references/base/bootstrapping.md +0 -2
- package/wiki/references/base/components.md +2 -2
- package/wiki/references/base/controllers.md +0 -1
- package/wiki/references/base/datasources.md +1 -1
- package/wiki/references/base/dependency-injection.md +1 -1
- package/wiki/references/base/filter-system/quick-reference.md +0 -14
- package/wiki/references/base/middlewares.md +0 -8
- package/wiki/references/base/providers.md +0 -9
- package/wiki/references/base/services.md +0 -1
- package/wiki/references/components/authentication/api.md +444 -0
- package/wiki/references/components/authentication/errors.md +177 -0
- package/wiki/references/components/authentication/index.md +571 -0
- package/wiki/references/components/authentication/usage.md +781 -0
- package/wiki/references/components/health-check.md +292 -103
- package/wiki/references/components/index.md +14 -12
- package/wiki/references/components/mail/api.md +505 -0
- package/wiki/references/components/mail/errors.md +176 -0
- package/wiki/references/components/mail/index.md +535 -0
- package/wiki/references/components/mail/usage.md +404 -0
- package/wiki/references/components/request-tracker.md +229 -25
- package/wiki/references/components/socket-io/api.md +1051 -0
- package/wiki/references/components/socket-io/errors.md +119 -0
- package/wiki/references/components/socket-io/index.md +410 -0
- package/wiki/references/components/socket-io/usage.md +322 -0
- package/wiki/references/components/static-asset/api.md +261 -0
- package/wiki/references/components/static-asset/errors.md +89 -0
- package/wiki/references/components/static-asset/index.md +617 -0
- package/wiki/references/components/static-asset/usage.md +364 -0
- package/wiki/references/components/swagger.md +390 -110
- package/wiki/references/components/template/api-page.md +125 -0
- package/wiki/references/components/template/errors-page.md +100 -0
- package/wiki/references/components/template/index.md +104 -0
- package/wiki/references/components/template/setup-page.md +134 -0
- package/wiki/references/components/template/single-page.md +132 -0
- package/wiki/references/components/template/usage-page.md +127 -0
- package/wiki/references/components/websocket/api.md +508 -0
- package/wiki/references/components/websocket/errors.md +123 -0
- package/wiki/references/components/websocket/index.md +453 -0
- package/wiki/references/components/websocket/usage.md +475 -0
- package/wiki/references/helpers/cron/index.md +224 -0
- package/wiki/references/helpers/crypto/index.md +537 -0
- package/wiki/references/helpers/env/index.md +214 -0
- package/wiki/references/helpers/error/index.md +232 -0
- package/wiki/references/helpers/index.md +16 -15
- package/wiki/references/helpers/inversion/index.md +608 -0
- package/wiki/references/helpers/logger/index.md +600 -0
- package/wiki/references/helpers/network/api.md +986 -0
- package/wiki/references/helpers/network/index.md +620 -0
- package/wiki/references/helpers/queue/index.md +589 -0
- package/wiki/references/helpers/redis/index.md +495 -0
- package/wiki/references/helpers/socket-io/api.md +497 -0
- package/wiki/references/helpers/socket-io/index.md +513 -0
- package/wiki/references/helpers/storage/api.md +705 -0
- package/wiki/references/helpers/storage/index.md +583 -0
- package/wiki/references/helpers/template/index.md +66 -0
- package/wiki/references/helpers/template/single-page.md +126 -0
- package/wiki/references/helpers/testing/index.md +510 -0
- package/wiki/references/helpers/types/index.md +512 -0
- package/wiki/references/helpers/uid/index.md +272 -0
- package/wiki/references/helpers/websocket/api.md +736 -0
- package/wiki/references/helpers/websocket/index.md +574 -0
- package/wiki/references/helpers/worker-thread/index.md +470 -0
- package/wiki/references/quick-reference.md +3 -18
- package/wiki/references/utilities/jsx.md +1 -8
- package/wiki/references/utilities/statuses.md +0 -7
- package/wiki/references/components/authentication.md +0 -476
- package/wiki/references/components/mail.md +0 -687
- package/wiki/references/components/socket-io.md +0 -562
- package/wiki/references/components/static-asset.md +0 -1277
- package/wiki/references/helpers/cron.md +0 -108
- package/wiki/references/helpers/crypto.md +0 -132
- package/wiki/references/helpers/env.md +0 -83
- package/wiki/references/helpers/error.md +0 -97
- package/wiki/references/helpers/inversion.md +0 -176
- package/wiki/references/helpers/logger.md +0 -296
- package/wiki/references/helpers/network.md +0 -396
- package/wiki/references/helpers/queue.md +0 -150
- package/wiki/references/helpers/redis.md +0 -142
- package/wiki/references/helpers/socket-io.md +0 -932
- package/wiki/references/helpers/storage.md +0 -665
- package/wiki/references/helpers/testing.md +0 -133
- package/wiki/references/helpers/types.md +0 -167
- package/wiki/references/helpers/uid.md +0 -167
- package/wiki/references/helpers/worker-thread.md +0 -178
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# Authentication -- API Reference
|
|
2
|
+
|
|
3
|
+
> Architecture, service internals, strategy registry, and controller factory. See [Setup & Configuration](./) for initial setup.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────────────────────────────────────────────────┐
|
|
9
|
+
│ Application │
|
|
10
|
+
│ │
|
|
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
|
+
└──────────────────┘
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Tech Stack
|
|
45
|
+
|
|
46
|
+
| Technology | Purpose |
|
|
47
|
+
|------------|---------|
|
|
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 |
|
|
50
|
+
| **Hono middleware** | Route-level authentication integration via `createMiddleware` from `hono/factory` |
|
|
51
|
+
| **Drizzle ORM** | Database access for user lookup (in your implementation) |
|
|
52
|
+
| **lodash/isEmpty** | Used in strategy registry for name validation |
|
|
53
|
+
|
|
54
|
+
## Component Private Methods
|
|
55
|
+
|
|
56
|
+
The `AuthenticateComponent` uses four private methods during its `binding()` lifecycle:
|
|
57
|
+
|
|
58
|
+
| Method | Purpose |
|
|
59
|
+
|--------|---------|
|
|
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. |
|
|
62
|
+
| `defineBasicAuth(opts)` | Validates `verifyCredentials` callback presence, binds `BasicTokenService` as a service. Logs debug if skipped. |
|
|
63
|
+
| `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. |
|
|
65
|
+
|
|
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.
|
|
68
|
+
|
|
69
|
+
## Strategy Registry
|
|
70
|
+
|
|
71
|
+
<code v-pre>AuthenticationStrategyRegistry<E extends Env = Env></code> is a **singleton** that manages all registered strategies. It extends `BaseHelper`.
|
|
72
|
+
|
|
73
|
+
### API
|
|
74
|
+
|
|
75
|
+
| Method | Signature | Returns | Description |
|
|
76
|
+
|--------|-----------|---------|-------------|
|
|
77
|
+
| `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<{ name: string; strategy: TClass<IAuthenticationStrategy<E>> }> }) => 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 |
|
|
82
|
+
|
|
83
|
+
**Registration:**
|
|
84
|
+
```typescript
|
|
85
|
+
AuthenticationStrategyRegistry.getInstance().register({
|
|
86
|
+
container: this,
|
|
87
|
+
strategies: [
|
|
88
|
+
{ name: Authentication.STRATEGY_JWT, strategy: JWTAuthenticationStrategy },
|
|
89
|
+
{ name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
> [!NOTE]
|
|
95
|
+
> `register()` returns `this`, enabling method chaining if needed.
|
|
96
|
+
|
|
97
|
+
**How it works:**
|
|
98
|
+
- Strategies are stored in an internal <code v-pre>Map<string, { container, strategyClass }></code> and also bound to the DI container as singletons
|
|
99
|
+
- 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
|
+
```
|
|
110
|
+
|
|
111
|
+
**Middleware creation:**
|
|
112
|
+
|
|
113
|
+
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)
|
|
116
|
+
3. Reads `strategies` and `mode` from the provided options
|
|
117
|
+
4. Executes strategies based on mode (`any` or `all`)
|
|
118
|
+
5. On success, sets `Authentication.CURRENT_USER` and `Authentication.AUDIT_USER_ID` on context
|
|
119
|
+
6. On failure, throws 401 with list of tried strategies
|
|
120
|
+
|
|
121
|
+
### Standalone `authenticate()` Function
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
export const authenticate = (opts: { strategies: string[]; mode?: TAuthMode }) => {
|
|
125
|
+
return AuthenticationStrategyRegistry.getInstance().authenticate(opts);
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
This is the primary export for creating auth middleware. It delegates directly to the singleton registry.
|
|
130
|
+
|
|
131
|
+
> [!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.
|
|
133
|
+
|
|
134
|
+
## JWTTokenService
|
|
135
|
+
|
|
136
|
+
All methods are instance methods on <code v-pre>JWTTokenService<E extends Env = Env></code>, which extends `BaseService`.
|
|
137
|
+
|
|
138
|
+
### JWTAuthenticationStrategy
|
|
139
|
+
|
|
140
|
+
Extends `BaseHelper` and implements <code v-pre>IAuthenticationStrategy<E></code>. Generic on <code v-pre><E extends Env = Env></code>.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
class JWTAuthenticationStrategy<E extends Env = Env>
|
|
144
|
+
extends BaseHelper
|
|
145
|
+
implements IAuthenticationStrategy<E>
|
|
146
|
+
{
|
|
147
|
+
name = Authentication.STRATEGY_JWT; // 'jwt'
|
|
148
|
+
|
|
149
|
+
constructor(
|
|
150
|
+
@inject({ key: 'services.JWTTokenService' })
|
|
151
|
+
private service: JWTTokenService<E>,
|
|
152
|
+
) { ... }
|
|
153
|
+
|
|
154
|
+
authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
155
|
+
const token = this.service.extractCredentials(context);
|
|
156
|
+
return this.service.verify(token);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Methods
|
|
162
|
+
|
|
163
|
+
| Method | Signature | Description |
|
|
164
|
+
|--------|-----------|-------------|
|
|
165
|
+
| `extractCredentials` | <code v-pre>(context: TContext<E, string>) => { type: string; token: string }</code> | Extracts Bearer token from Authorization header |
|
|
166
|
+
| `verify` | <code v-pre>(opts: { type: string; token: string }) => Promise<IJWTTokenPayload></code> | Verifies JWT signature via `jose.jwtVerify()` and decrypts payload |
|
|
167
|
+
| `generate` | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn?: TGetTokenExpiresFn }) => Promise<string></code> | Encrypts payload, signs JWT with configurable expiration |
|
|
168
|
+
| `getSigner` | <code v-pre>(opts: { payload: IJWTTokenPayload; getTokenExpiresFn: TGetTokenExpiresFn }) => Promise<SignJWT></code> | Creates a `jose.SignJWT` instance with encrypted payload, iat, exp, nbf |
|
|
169
|
+
| `encryptPayload` | <code v-pre>(payload: IJWTTokenPayload) => Record<string, string></code> | AES-encrypts non-standard JWT fields (keys + values) |
|
|
170
|
+
| `decryptPayload` | <code v-pre>(opts: { result: JWTVerifyResult<IJWTTokenPayload> }) => IJWTTokenPayload</code> | Decrypts AES-encrypted fields back to IJWTTokenPayload |
|
|
171
|
+
|
|
172
|
+
### Static Fields
|
|
173
|
+
|
|
174
|
+
- `JWT_COMMON_FIELDS`: <code v-pre>Set<'iss' | 'sub' | 'aud' | 'jti' | 'nbf' | 'exp' | 'iat'></code> -- fields preserved as-is during encryption
|
|
175
|
+
|
|
176
|
+
### Protected Fields
|
|
177
|
+
|
|
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
|
|
181
|
+
|
|
182
|
+
### Constructor Behavior
|
|
183
|
+
|
|
184
|
+
The constructor validates all three required options and throws immediately (status 500) if any are missing:
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
constructor(
|
|
188
|
+
@inject({ key: AuthenticateBindingKeys.JWT_OPTIONS })
|
|
189
|
+
protected options: IJWTTokenServiceOptions,
|
|
190
|
+
) {
|
|
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
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
## BasicTokenService
|
|
200
|
+
|
|
201
|
+
All methods are instance methods on <code v-pre>BasicTokenService<E extends Env = Env></code>, which extends `BaseService`.
|
|
202
|
+
|
|
203
|
+
### BasicAuthenticationStrategy
|
|
204
|
+
|
|
205
|
+
Extends `BaseHelper` and implements <code v-pre>IAuthenticationStrategy<E></code>. Generic on <code v-pre><E extends Env = Env></code>.
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
class BasicAuthenticationStrategy<E extends Env = Env>
|
|
209
|
+
extends BaseHelper
|
|
210
|
+
implements IAuthenticationStrategy<E>
|
|
211
|
+
{
|
|
212
|
+
name = Authentication.STRATEGY_BASIC; // 'basic'
|
|
213
|
+
|
|
214
|
+
constructor(
|
|
215
|
+
@inject({ key: 'services.BasicTokenService' })
|
|
216
|
+
private service: BasicTokenService<E>,
|
|
217
|
+
) { ... }
|
|
218
|
+
|
|
219
|
+
async authenticate(context: TContext<E, string>): Promise<IAuthUser> {
|
|
220
|
+
const credentials = this.service.extractCredentials(context);
|
|
221
|
+
return this.service.verify({ credentials, context });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Methods
|
|
227
|
+
|
|
228
|
+
| Method | Signature | Description |
|
|
229
|
+
|--------|-----------|-------------|
|
|
230
|
+
| `extractCredentials` | <code v-pre>(context: TContext<E, string>) => { username: string; password: string }</code> | Decodes Base64 <code v-pre>Authorization: Basic <base64></code> header |
|
|
231
|
+
| `verify` | <code v-pre>(opts: { credentials: { username: string; password: string }; context: TContext<E, string> }) => Promise<IAuthUser></code> | Calls user-provided `verifyCredentials` callback |
|
|
232
|
+
|
|
233
|
+
### Private Fields
|
|
234
|
+
|
|
235
|
+
- `verifyCredentials` -- the callback function extracted from injected options
|
|
236
|
+
|
|
237
|
+
### Constructor Behavior
|
|
238
|
+
|
|
239
|
+
```typescript
|
|
240
|
+
constructor(
|
|
241
|
+
@inject({ key: AuthenticateBindingKeys.BASIC_OPTIONS })
|
|
242
|
+
protected options: IBasicTokenServiceOptions<E>,
|
|
243
|
+
) {
|
|
244
|
+
// Throws '[BasicTokenService] Invalid verifyCredentials function' if !options?.verifyCredentials
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Entity Column Helper Types
|
|
249
|
+
|
|
250
|
+
The following types are exported for use when extending the auth entity column helpers:
|
|
251
|
+
|
|
252
|
+
### Permission Types
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
type TPermissionOptions = {
|
|
256
|
+
idType?: 'string' | 'number';
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
type TPermissionCommonColumns = {
|
|
260
|
+
code: NotNull<PgTextBuilderInitial<...>>;
|
|
261
|
+
name: NotNull<PgTextBuilderInitial<...>>;
|
|
262
|
+
subject: NotNull<PgTextBuilderInitial<...>>;
|
|
263
|
+
pType: NotNull<PgTextBuilderInitial<...>>;
|
|
264
|
+
action: NotNull<PgTextBuilderInitial<...>>;
|
|
265
|
+
scope: NotNull<PgTextBuilderInitial<...>>;
|
|
266
|
+
};
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Permission Mapping Types
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
type TPermissionMappingOptions = {
|
|
273
|
+
idType?: 'string' | 'number';
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
type TPermissionMappingCommonColumns = {
|
|
277
|
+
effect: PgTextBuilderInitial<...>;
|
|
278
|
+
};
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### User Role Types
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
type TUserRoleOptions = {
|
|
285
|
+
idType?: 'string' | 'number';
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
type TUserRoleCommonColumns = ReturnType<
|
|
289
|
+
typeof generatePrincipalColumnDefs<'principal', 'string' | 'number'>
|
|
290
|
+
>;
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
## Controller Factory
|
|
294
|
+
|
|
295
|
+
The `defineAuthController()` function dynamically creates a controller class at runtime using decorator composition:
|
|
296
|
+
|
|
297
|
+
**How it works:**
|
|
298
|
+
|
|
299
|
+
1. **Class creation:** A new class is created dynamically with `class AuthController extends BaseController {}` inside the factory closure
|
|
300
|
+
2. **Decorator application:** The `@controller({ path: restPath })` decorator is applied to set the base path. The controller is created with `isStrict: true`
|
|
301
|
+
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'`
|
|
303
|
+
- Service must implement `IAuthService` interface
|
|
304
|
+
4. **Route definition:** Routes are defined in the controller's `binding()` method using `this.defineRoute()`
|
|
305
|
+
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.
|
|
306
|
+
|
|
307
|
+
**Factory signature:**
|
|
308
|
+
|
|
309
|
+
```typescript
|
|
310
|
+
function defineAuthController(opts: TDefineAuthControllerOpts): typeof AuthController;
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
> [!NOTE]
|
|
314
|
+
> The factory also exports `JWTTokenPayloadSchema`, a Zod schema used for the `/who-am-i` response validation.
|
|
315
|
+
|
|
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
|
+
**Service resolution:**
|
|
422
|
+
|
|
423
|
+
The factory applies `@inject` programmatically to constructor parameter 0:
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
// Inside defineAuthController, after class definition:
|
|
427
|
+
inject({ key: serviceKey })(AuthController, undefined, 0);
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
This is equivalent to:
|
|
431
|
+
```typescript
|
|
432
|
+
constructor(
|
|
433
|
+
@inject({ key: serviceKey })
|
|
434
|
+
authService: IAuthService,
|
|
435
|
+
) { ... }
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
If the service is not bound, the component will throw: `"[AuthController] Failed to init auth controller | Invalid injectable authentication service!"`
|
|
439
|
+
|
|
440
|
+
## See Also
|
|
441
|
+
|
|
442
|
+
- [Setup & Configuration](./) -- Binding keys, options interfaces, and initial setup
|
|
443
|
+
- [Usage & Examples](./usage) -- Securing routes, auth flows, and API endpoints
|
|
444
|
+
- [Error Reference](./errors) -- Error messages and troubleshooting
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Authentication -- Error Reference
|
|
2
|
+
|
|
3
|
+
> Complete error messages and troubleshooting for the authentication module. See [Setup & Configuration](./) for initial setup.
|
|
4
|
+
|
|
5
|
+
## Complete Error Reference
|
|
6
|
+
|
|
7
|
+
All error messages from the authentication module, organized by source:
|
|
8
|
+
|
|
9
|
+
### Component Errors (AuthenticateComponent)
|
|
10
|
+
|
|
11
|
+
| Error Message | Status | Method |
|
|
12
|
+
|---------------|--------|--------|
|
|
13
|
+
| `[AuthenticateComponent] At least one of jwtOptions or basicOptions must be provided` | 500 | `validateOptions` |
|
|
14
|
+
| <code v-pre>[defineJWTAuth] Invalid jwtSecret | Provided: {{jwtSecret}}</code> | 500 | `defineJWTAuth` |
|
|
15
|
+
| <code v-pre>[defineJWTAuth] Invalid applicationSecret | Provided: {{applicationSecret}}</code> | 500 | `defineJWTAuth` |
|
|
16
|
+
| `[defineJWTAuth] getTokenExpiresFn is required` | 500 | `defineJWTAuth` |
|
|
17
|
+
| `[defineBasicAuth] verifyCredentials function is required` | 500 | `defineBasicAuth` |
|
|
18
|
+
| `[defineControllers] Auth controller requires jwtOptions to be configured` | 500 | `defineControllers` |
|
|
19
|
+
|
|
20
|
+
### JWTTokenService Errors
|
|
21
|
+
|
|
22
|
+
| Error Message | Status | Method |
|
|
23
|
+
|---------------|--------|--------|
|
|
24
|
+
| `[JWTTokenService] Invalid jwtSecret` | 500 | `constructor` |
|
|
25
|
+
| `[JWTTokenService] Invalid applicationSecret` | 500 | `constructor` |
|
|
26
|
+
| `[JWTTokenService] Invalid getTokenExpiresFn` | 500 | `constructor` |
|
|
27
|
+
| `Unauthorized user! Missing authorization header` | 401 | `extractCredentials` |
|
|
28
|
+
| `Unauthorized user! Invalid schema of request token!` | 401 | `extractCredentials` |
|
|
29
|
+
| <code v-pre>Authorization header value is invalid format. It must follow the pattern: 'Bearer xx.yy.zz' where xx.yy.zz is a valid JWT token.</code> | 401 | `extractCredentials` |
|
|
30
|
+
| `[verify] Invalid request token!` | 401 | `verify` |
|
|
31
|
+
| <code v-pre>[verify] Failed to verify token | Message: {{error.message}}</code> | 401 | `verify` |
|
|
32
|
+
| `[generate] Invalid token payload!` | 401 | `generate` |
|
|
33
|
+
| <code v-pre>[generate] Failed to generate token | Error: {{error.message}}</code> | 500 | `generate` |
|
|
34
|
+
|
|
35
|
+
### BasicTokenService Errors
|
|
36
|
+
|
|
37
|
+
| Error Message | Status | Method |
|
|
38
|
+
|---------------|--------|--------|
|
|
39
|
+
| `[BasicTokenService] Invalid verifyCredentials function` | 500 | `constructor` |
|
|
40
|
+
| `Unauthorized! Missing authorization header` | 401 | `extractCredentials` |
|
|
41
|
+
| `Unauthorized! Invalid authorization schema, expected Basic` | 401 | `extractCredentials` |
|
|
42
|
+
| `Unauthorized! Invalid authorization header format` | 401 | `extractCredentials` |
|
|
43
|
+
| `Unauthorized! Invalid base64 credentials format` | 401 | `extractCredentials` |
|
|
44
|
+
| `Unauthorized! Invalid username or password` | 401 | `verify` |
|
|
45
|
+
|
|
46
|
+
### Strategy Registry Errors
|
|
47
|
+
|
|
48
|
+
| Error Message | Status | Method |
|
|
49
|
+
|---------------|--------|--------|
|
|
50
|
+
| <code v-pre>[getStrategyKey] Invalid strategy name | name: {{name}}</code> | 500 | `getStrategyKey` |
|
|
51
|
+
| <code v-pre>[executeStrategy] Strategy not found: {{strategyName}}</code> | 500 | `executeStrategy` |
|
|
52
|
+
| <code v-pre>[executeStrategy] strategy: {{strategyName}} | Authentication Strategy NOT FOUND</code> | 500 | `executeStrategy` |
|
|
53
|
+
| <code v-pre>Authentication failed. Tried strategies: {{strategies}}</code> | 401 | `authenticate` (any mode) |
|
|
54
|
+
| `Failed to identify authenticated user!` | 401 | `authenticate` (all mode) |
|
|
55
|
+
| <code v-pre>Invalid authentication mode | mode: {{mode}}</code> | 500 | `authenticate` (default) |
|
|
56
|
+
|
|
57
|
+
### Controller Factory Errors
|
|
58
|
+
|
|
59
|
+
| Error Message | Status | Method |
|
|
60
|
+
|---------------|--------|--------|
|
|
61
|
+
| `[AuthController] Failed to init auth controller | Invalid injectable authentication service!` | 500 | `constructor` |
|
|
62
|
+
|
|
63
|
+
## Troubleshooting
|
|
64
|
+
|
|
65
|
+
### "[AuthenticateComponent] At least one of jwtOptions or basicOptions must be provided"
|
|
66
|
+
|
|
67
|
+
**Cause:** The `AuthenticateComponent` requires at least one authentication method to be configured. Neither `JWT_OPTIONS` nor `BASIC_OPTIONS` was bound in the DI container.
|
|
68
|
+
|
|
69
|
+
**Fix:** Bind at least one set of options before registering the component:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
this.bind<IJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
73
|
+
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET,
|
|
74
|
+
jwtSecret: process.env.APP_ENV_JWT_SECRET,
|
|
75
|
+
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
76
|
+
});
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### "[defineJWTAuth] Invalid jwtSecret" / "[defineJWTAuth] Invalid applicationSecret"
|
|
80
|
+
|
|
81
|
+
**Cause:** The JWT secret or application secret is missing, empty, or set to the default placeholder `'unknown_secret'`. The component's `defineJWTAuth()` method validates these values during binding. The error message includes the actual provided value (e.g., <code v-pre>[defineJWTAuth] Invalid jwtSecret | Provided: {{jwtSecret}}</code>).
|
|
82
|
+
|
|
83
|
+
**Fix:** Set strong, unique values for both secrets in your environment variables:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
APP_ENV_APPLICATION_SECRET=your-strong-application-secret
|
|
87
|
+
APP_ENV_JWT_SECRET=your-strong-jwt-secret
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### "[defineJWTAuth] getTokenExpiresFn is required"
|
|
91
|
+
|
|
92
|
+
**Cause:** The `getTokenExpiresFn` function was not provided in the JWT options.
|
|
93
|
+
|
|
94
|
+
**Fix:** Include a `getTokenExpiresFn` in your JWT options binding:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
this.bind<IJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
98
|
+
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET,
|
|
99
|
+
jwtSecret: process.env.APP_ENV_JWT_SECRET,
|
|
100
|
+
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
101
|
+
});
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### "[defineBasicAuth] verifyCredentials function is required"
|
|
105
|
+
|
|
106
|
+
**Cause:** `BASIC_OPTIONS` was bound but without a `verifyCredentials` callback function.
|
|
107
|
+
|
|
108
|
+
**Fix:** Provide a `verifyCredentials` function in the Basic options:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
this.bind<IBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS }).toValue({
|
|
112
|
+
verifyCredentials: async (opts) => {
|
|
113
|
+
// Your credential verification logic
|
|
114
|
+
return { userId: user.id };
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### "[defineControllers] Auth controller requires jwtOptions to be configured"
|
|
120
|
+
|
|
121
|
+
**Cause:** The built-in auth controller (`useAuthController: true`) was enabled without binding JWT options. The auth controller requires JWT for token generation.
|
|
122
|
+
|
|
123
|
+
**Fix:** Always bind `JWT_OPTIONS` when using the auth controller:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
this.bind<TAuthenticationRestOptions>({ key: AuthenticateBindingKeys.REST_OPTIONS }).toValue({
|
|
127
|
+
useAuthController: true,
|
|
128
|
+
controllerOpts: { restPath: '/auth' },
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// This is required when useAuthController is true
|
|
132
|
+
this.bind<IJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
133
|
+
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET,
|
|
134
|
+
jwtSecret: process.env.APP_ENV_JWT_SECRET,
|
|
135
|
+
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### "Authentication failed. Tried strategies: jwt, basic"
|
|
140
|
+
|
|
141
|
+
**Cause:** All configured strategies failed to authenticate the request. In `'any'` mode, every strategy is tried in order; if none succeeds, this error is thrown with a `401 Unauthorized` status.
|
|
142
|
+
|
|
143
|
+
**Fix:** Verify the client is sending the correct `Authorization` header:
|
|
144
|
+
- For JWT: `Authorization: Bearer <token>`
|
|
145
|
+
- For Basic: `Authorization: Basic <base64(username:password)>`
|
|
146
|
+
|
|
147
|
+
Check that the token is not expired and the credentials are valid.
|
|
148
|
+
|
|
149
|
+
### "[AuthController] Failed to init auth controller | Invalid injectable authentication service!"
|
|
150
|
+
|
|
151
|
+
**Cause:** The auth controller factory could not resolve the auth service from the DI container. The service key (default `'services.AuthenticationService'`) is not bound.
|
|
152
|
+
|
|
153
|
+
**Fix:** Register your `AuthenticationService` before registering the component:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
this.service(AuthenticationService);
|
|
157
|
+
// Then register the component
|
|
158
|
+
this.component(AuthenticateComponent);
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### "[JWTTokenService] Invalid jwtSecret" / "[JWTTokenService] Invalid applicationSecret" / "[JWTTokenService] Invalid getTokenExpiresFn"
|
|
162
|
+
|
|
163
|
+
**Cause:** The `JWTTokenService` constructor validates its injected options. These errors (status 500) occur when the service is instantiated with missing or falsy values. This is separate from the component-level `defineJWTAuth` validation and fires during DI resolution.
|
|
164
|
+
|
|
165
|
+
**Fix:** Ensure the bound `IJWTTokenServiceOptions` has all required fields populated with valid values.
|
|
166
|
+
|
|
167
|
+
### "[BasicTokenService] Invalid verifyCredentials function"
|
|
168
|
+
|
|
169
|
+
**Cause:** The `BasicTokenService` constructor validates that the injected `verifyCredentials` option is present. This error (status 500) fires during DI resolution.
|
|
170
|
+
|
|
171
|
+
**Fix:** Ensure the bound `IBasicTokenServiceOptions` includes a `verifyCredentials` function.
|
|
172
|
+
|
|
173
|
+
## See Also
|
|
174
|
+
|
|
175
|
+
- [Setup & Configuration](./) -- Binding keys, options interfaces, and initial setup
|
|
176
|
+
- [Usage & Examples](./usage) -- Securing routes, auth flows, and API endpoints
|
|
177
|
+
- [API Reference](./api) -- Architecture, service internals, and strategy registry
|