@venizia/ignis-docs 0.0.4-1 → 0.0.4-2
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/api-usage-examples.md +1 -0
- package/wiki/best-practices/code-style-standards/advanced-patterns.md +259 -0
- package/wiki/best-practices/code-style-standards/constants-configuration.md +225 -0
- package/wiki/best-practices/code-style-standards/control-flow.md +245 -0
- package/wiki/best-practices/code-style-standards/documentation.md +221 -0
- package/wiki/best-practices/code-style-standards/function-patterns.md +142 -0
- package/wiki/best-practices/code-style-standards/index.md +110 -0
- package/wiki/best-practices/code-style-standards/naming-conventions.md +174 -0
- package/wiki/best-practices/code-style-standards/route-definitions.md +150 -0
- package/wiki/best-practices/code-style-standards/tooling.md +155 -0
- package/wiki/best-practices/code-style-standards/type-safety.md +165 -0
- package/wiki/best-practices/common-pitfalls.md +164 -3
- package/wiki/best-practices/contribution-workflow.md +1 -1
- package/wiki/best-practices/data-modeling.md +102 -2
- package/wiki/best-practices/error-handling.md +468 -0
- package/wiki/best-practices/index.md +204 -21
- package/wiki/best-practices/performance-optimization.md +180 -0
- package/wiki/best-practices/security-guidelines.md +249 -0
- package/wiki/best-practices/testing-strategies.md +620 -0
- package/wiki/changelogs/2026-01-05-range-queries-content-range.md +184 -0
- package/wiki/changelogs/2026-01-06-basic-authentication.md +103 -0
- package/wiki/changelogs/2026-01-07-controller-route-customization.md +209 -0
- package/wiki/changelogs/index.md +3 -0
- package/wiki/guides/core-concepts/components-guide.md +1 -1
- package/wiki/guides/core-concepts/persistent/models.md +10 -0
- package/wiki/guides/tutorials/complete-installation.md +1 -1
- package/wiki/guides/tutorials/testing.md +1 -1
- package/wiki/references/base/components.md +47 -29
- package/wiki/references/base/controllers.md +215 -18
- package/wiki/references/base/filter-system/fields-order-pagination.md +84 -0
- package/wiki/references/base/middlewares.md +33 -1
- package/wiki/references/base/models.md +40 -2
- package/wiki/references/base/repositories/index.md +2 -0
- package/wiki/references/components/authentication.md +261 -247
- package/wiki/references/helpers/index.md +1 -1
- package/wiki/references/src-details/core.md +1 -1
- package/wiki/best-practices/code-style-standards.md +0 -1193
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Authentication Component
|
|
2
2
|
|
|
3
|
-
JWT
|
|
3
|
+
JWT and Basic authentication system for Ignis applications with multi-strategy support.
|
|
4
4
|
|
|
5
5
|
## Quick Reference
|
|
6
6
|
|
|
@@ -9,7 +9,9 @@ JWT-based authentication and authorization system for Ignis applications.
|
|
|
9
9
|
| **AuthenticateComponent** | Main component registering auth services and controllers |
|
|
10
10
|
| **AuthenticationStrategyRegistry** | Singleton managing available auth strategies |
|
|
11
11
|
| **JWTAuthenticationStrategy** | JWT verification using `JWTTokenService` |
|
|
12
|
-
| **
|
|
12
|
+
| **BasicAuthenticationStrategy** | Basic HTTP authentication using `BasicTokenService` |
|
|
13
|
+
| **JWTTokenService** | Generate, verify, encrypt/decrypt JWT tokens |
|
|
14
|
+
| **BasicTokenService** | Extract and verify Basic auth credentials |
|
|
13
15
|
| **IAuthService** | Interface for custom auth implementation (sign-in, sign-up) |
|
|
14
16
|
| **defineAuthController** | Factory function for creating custom auth controllers |
|
|
15
17
|
|
|
@@ -17,31 +19,60 @@ JWT-based authentication and authorization system for Ignis applications.
|
|
|
17
19
|
|
|
18
20
|
| Variable | Purpose | Required |
|
|
19
21
|
|----------|---------|----------|
|
|
20
|
-
| `APP_ENV_APPLICATION_SECRET` | Encrypt JWT payload |
|
|
21
|
-
| `APP_ENV_JWT_SECRET` | Sign and verify JWT signature |
|
|
22
|
+
| `APP_ENV_APPLICATION_SECRET` | Encrypt JWT payload | Required for JWT |
|
|
23
|
+
| `APP_ENV_JWT_SECRET` | Sign and verify JWT signature | Required for JWT |
|
|
22
24
|
| `APP_ENV_JWT_EXPIRES_IN` | Token expiration (seconds) | Optional |
|
|
23
25
|
|
|
24
|
-
###
|
|
26
|
+
### Binding Keys
|
|
27
|
+
|
|
28
|
+
The authentication component uses **separate binding keys** for each configuration type:
|
|
29
|
+
|
|
30
|
+
| Binding Key | Type | Description |
|
|
31
|
+
|-------------|------|-------------|
|
|
32
|
+
| `AuthenticateBindingKeys.REST_OPTIONS` | `TAuthenticationRestOptions` | REST controller configuration |
|
|
33
|
+
| `AuthenticateBindingKeys.JWT_OPTIONS` | `IJWTTokenServiceOptions` | JWT token configuration |
|
|
34
|
+
| `AuthenticateBindingKeys.BASIC_OPTIONS` | `IBasicTokenServiceOptions` | Basic auth configuration |
|
|
35
|
+
|
|
36
|
+
### REST Options Configuration
|
|
25
37
|
|
|
26
38
|
| Option | Type | Description |
|
|
27
39
|
|--------|------|-------------|
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
| `useAuthController` | `boolean` | Enable/disable built-in auth controller (default: `false`) |
|
|
41
|
+
| `controllerOpts` | `TDefineAuthControllerOpts` | Configuration for built-in auth controller (required if `useAuthController` is `true`) |
|
|
42
|
+
| `controllerOpts.restPath` | `string` | Base path for auth endpoints (default: `/auth`) |
|
|
43
|
+
| `controllerOpts.serviceKey` | `string` | Dependency injection key for auth service |
|
|
44
|
+
| `controllerOpts.requireAuthenticatedSignUp` | `boolean` | Whether sign-up requires authentication (default: `false`) |
|
|
45
|
+
| `controllerOpts.payload` | `object` | Custom Zod schemas for request/response payloads |
|
|
46
|
+
|
|
47
|
+
::: warning IMPORTANT
|
|
48
|
+
At least one of `JWT_OPTIONS` or `BASIC_OPTIONS` must be bound. If neither is configured, the component will throw an error.
|
|
49
|
+
:::
|
|
50
|
+
|
|
51
|
+
### Route Configuration Options
|
|
52
|
+
|
|
53
|
+
| Option | Type | Description |
|
|
54
|
+
|--------|------|-------------|
|
|
55
|
+
| `authStrategies` | `TAuthStrategy[]` | Array of strategy names to use (e.g., `['jwt']`, `['jwt', 'basic']`) |
|
|
56
|
+
| `authMode` | `'any' \| 'all'` | How to handle multiple strategies (default: `'any'`) |
|
|
57
|
+
| `skipAuth` | `boolean` | Skip authentication for this route (default: `false`) |
|
|
58
|
+
|
|
59
|
+
### Auth Modes
|
|
60
|
+
|
|
61
|
+
| Mode | Behavior |
|
|
62
|
+
|------|----------|
|
|
63
|
+
| `'any'` | First successful strategy wins (fallback mode) |
|
|
64
|
+
| `'all'` | All strategies must pass (MFA mode) |
|
|
36
65
|
|
|
37
66
|
## Architecture Components
|
|
38
67
|
|
|
39
68
|
- **`AuthenticateComponent`**: Registers all necessary services and optionally the authentication controller
|
|
40
69
|
- **`AuthenticationStrategyRegistry`**: Singleton managing authentication strategies
|
|
41
70
|
- **`JWTAuthenticationStrategy`**: JWT strategy implementation using `JWTTokenService`
|
|
42
|
-
- **`
|
|
71
|
+
- **`BasicAuthenticationStrategy`**: Basic HTTP auth strategy using `BasicTokenService`
|
|
72
|
+
- **`JWTTokenService`**: Generates, verifies, encrypts/decrypts JWT payloads
|
|
73
|
+
- **`BasicTokenService`**: Extracts and verifies Basic auth credentials
|
|
43
74
|
- **`defineAuthController`**: Factory function to create customizable authentication controller
|
|
44
|
-
- **Protected Routes**: Use `authStrategies` in route configs to secure endpoints
|
|
75
|
+
- **Protected Routes**: Use `authStrategies` and `authMode` in route configs to secure endpoints
|
|
45
76
|
|
|
46
77
|
## Implementation Details
|
|
47
78
|
|
|
@@ -60,7 +91,7 @@ Configure the authentication feature using environment variables:
|
|
|
60
91
|
- `APP_ENV_JWT_EXPIRES_IN`: The JWT expiration time in seconds.
|
|
61
92
|
|
|
62
93
|
::: danger SECURITY NOTE
|
|
63
|
-
Both `APP_ENV_APPLICATION_SECRET` and `APP_ENV_JWT_SECRET` are **mandatory
|
|
94
|
+
Both `APP_ENV_APPLICATION_SECRET` and `APP_ENV_JWT_SECRET` are **mandatory** when using JWT authentication. For security purposes, you must set these to strong, unique secret values.
|
|
64
95
|
:::
|
|
65
96
|
|
|
66
97
|
**Example `.env` file:**
|
|
@@ -75,157 +106,209 @@ APP_ENV_JWT_EXPIRES_IN=86400
|
|
|
75
106
|
|
|
76
107
|
#### 1. Registering the Authentication Component
|
|
77
108
|
|
|
78
|
-
In `src/application.ts`, register the `AuthenticateComponent` and
|
|
109
|
+
In `src/application.ts`, register the `AuthenticateComponent` and authentication strategies.
|
|
79
110
|
|
|
80
|
-
**
|
|
111
|
+
**JWT Only Setup:**
|
|
81
112
|
|
|
82
113
|
```typescript
|
|
83
114
|
// src/application.ts
|
|
84
115
|
import {
|
|
85
116
|
AuthenticateComponent,
|
|
117
|
+
AuthenticateBindingKeys,
|
|
86
118
|
Authentication,
|
|
87
119
|
AuthenticationStrategyRegistry,
|
|
120
|
+
IJWTTokenServiceOptions,
|
|
88
121
|
JWTAuthenticationStrategy,
|
|
89
122
|
BaseApplication,
|
|
90
123
|
ValueOrPromise,
|
|
91
124
|
} from '@venizia/ignis';
|
|
92
|
-
import { AuthenticationService } from './services';
|
|
125
|
+
import { AuthenticationService } from './services';
|
|
93
126
|
|
|
94
127
|
export class Application extends BaseApplication {
|
|
95
|
-
// ...
|
|
96
|
-
|
|
97
128
|
registerAuth() {
|
|
98
129
|
this.service(AuthenticationService);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET,
|
|
106
|
-
jwtSecret: process.env.APP_ENV_JWT_SECRET,
|
|
107
|
-
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
108
|
-
},
|
|
130
|
+
|
|
131
|
+
// Bind JWT options
|
|
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),
|
|
109
136
|
});
|
|
137
|
+
|
|
138
|
+
this.component(AuthenticateComponent);
|
|
110
139
|
AuthenticationStrategyRegistry.getInstance().register({
|
|
111
140
|
container: this,
|
|
112
|
-
|
|
113
|
-
|
|
141
|
+
strategies: [
|
|
142
|
+
{ name: Authentication.STRATEGY_JWT, strategy: JWTAuthenticationStrategy },
|
|
143
|
+
],
|
|
114
144
|
});
|
|
115
145
|
}
|
|
116
146
|
|
|
117
147
|
preConfigure(): ValueOrPromise<void> {
|
|
118
|
-
// ...
|
|
119
148
|
this.registerAuth();
|
|
120
|
-
// ...
|
|
121
149
|
}
|
|
122
150
|
}
|
|
123
151
|
```
|
|
124
152
|
|
|
125
|
-
**
|
|
153
|
+
**Basic Auth Only Setup:**
|
|
126
154
|
|
|
127
155
|
```typescript
|
|
128
156
|
import {
|
|
129
157
|
AuthenticateComponent,
|
|
158
|
+
AuthenticateBindingKeys,
|
|
130
159
|
Authentication,
|
|
131
160
|
AuthenticationStrategyRegistry,
|
|
132
|
-
|
|
161
|
+
BasicAuthenticationStrategy,
|
|
162
|
+
IBasicTokenServiceOptions,
|
|
133
163
|
BaseApplication,
|
|
134
|
-
ValueOrPromise,
|
|
135
164
|
} from '@venizia/ignis';
|
|
136
|
-
import { z } from '@hono/zod-openapi';
|
|
137
|
-
import { AuthenticationService } from './services';
|
|
138
165
|
|
|
139
166
|
export class Application extends BaseApplication {
|
|
140
|
-
|
|
167
|
+
registerAuth() {
|
|
168
|
+
// Bind Basic auth options
|
|
169
|
+
this.bind<IBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS }).toValue({
|
|
170
|
+
verifyCredentials: async (opts) => {
|
|
171
|
+
const { credentials, context } = opts;
|
|
172
|
+
// Your verification logic here
|
|
173
|
+
const user = await this.userRepo.findByUsername(credentials.username);
|
|
174
|
+
if (user && await bcrypt.compare(credentials.password, user.passwordHash)) {
|
|
175
|
+
return { userId: user.id, roles: user.roles };
|
|
176
|
+
}
|
|
177
|
+
return null;
|
|
178
|
+
},
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
this.component(AuthenticateComponent);
|
|
182
|
+
AuthenticationStrategyRegistry.getInstance().register({
|
|
183
|
+
container: this,
|
|
184
|
+
strategies: [
|
|
185
|
+
{ name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
|
|
186
|
+
],
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**Combined JWT + Basic Auth Setup (with fallback):**
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import {
|
|
196
|
+
AuthenticateComponent,
|
|
197
|
+
AuthenticateBindingKeys,
|
|
198
|
+
Authentication,
|
|
199
|
+
AuthenticationStrategyRegistry,
|
|
200
|
+
BasicAuthenticationStrategy,
|
|
201
|
+
JWTAuthenticationStrategy,
|
|
202
|
+
IJWTTokenServiceOptions,
|
|
203
|
+
IBasicTokenServiceOptions,
|
|
204
|
+
TAuthenticationRestOptions,
|
|
205
|
+
BaseApplication,
|
|
206
|
+
} from '@venizia/ignis';
|
|
141
207
|
|
|
208
|
+
export class Application extends BaseApplication {
|
|
142
209
|
registerAuth() {
|
|
143
210
|
this.service(AuthenticationService);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}),
|
|
159
|
-
credential: z.object({
|
|
160
|
-
type: z.string(),
|
|
161
|
-
value: z.string(),
|
|
162
|
-
}),
|
|
163
|
-
})
|
|
164
|
-
},
|
|
165
|
-
response: {
|
|
166
|
-
schema: z.object({
|
|
167
|
-
token: z.string(),
|
|
168
|
-
})
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
signUp: {
|
|
172
|
-
request: {
|
|
173
|
-
schema: z.object({
|
|
174
|
-
username: z.string(),
|
|
175
|
-
email: z.string().email(),
|
|
176
|
-
password: z.string().min(8),
|
|
177
|
-
})
|
|
178
|
-
},
|
|
179
|
-
response: {
|
|
180
|
-
schema: z.object({
|
|
181
|
-
token: z.string(),
|
|
182
|
-
userId: z.string(),
|
|
183
|
-
})
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
changePassword: {
|
|
187
|
-
request: {
|
|
188
|
-
schema: z.object({
|
|
189
|
-
oldPassword: z.string(),
|
|
190
|
-
newPassword: z.string().min(8),
|
|
191
|
-
})
|
|
192
|
-
},
|
|
193
|
-
response: {
|
|
194
|
-
schema: z.object({
|
|
195
|
-
success: z.boolean(),
|
|
196
|
-
})
|
|
197
|
-
},
|
|
198
|
-
},
|
|
211
|
+
|
|
212
|
+
// Bind REST options (for auth controller)
|
|
213
|
+
this.bind<TAuthenticationRestOptions>({ key: AuthenticateBindingKeys.REST_OPTIONS }).toValue({
|
|
214
|
+
useAuthController: true,
|
|
215
|
+
controllerOpts: {
|
|
216
|
+
restPath: '/auth',
|
|
217
|
+
payload: {
|
|
218
|
+
signIn: {
|
|
219
|
+
request: { schema: SignInRequestSchema },
|
|
220
|
+
response: { schema: SignInResponseSchema },
|
|
221
|
+
},
|
|
222
|
+
signUp: {
|
|
223
|
+
request: { schema: SignUpRequestSchema },
|
|
224
|
+
response: { schema: SignUpResponseSchema },
|
|
199
225
|
},
|
|
200
226
|
},
|
|
201
227
|
},
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Bind JWT options
|
|
231
|
+
this.bind<IJWTTokenServiceOptions>({ key: AuthenticateBindingKeys.JWT_OPTIONS }).toValue({
|
|
232
|
+
applicationSecret: process.env.APP_ENV_APPLICATION_SECRET,
|
|
233
|
+
jwtSecret: process.env.APP_ENV_JWT_SECRET,
|
|
234
|
+
getTokenExpiresFn: () => Number(process.env.APP_ENV_JWT_EXPIRES_IN || 86400),
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Bind Basic auth options
|
|
238
|
+
this.bind<IBasicTokenServiceOptions>({ key: AuthenticateBindingKeys.BASIC_OPTIONS }).toValue({
|
|
239
|
+
verifyCredentials: async (opts) => {
|
|
240
|
+
const authenticateService = this.get<AuthenticationService>({
|
|
241
|
+
key: BindingKeys.build({
|
|
242
|
+
namespace: BindingNamespaces.SERVICE,
|
|
243
|
+
key: AuthenticationService.name,
|
|
244
|
+
}),
|
|
245
|
+
});
|
|
246
|
+
return authenticateService.signIn(opts.context, {
|
|
247
|
+
identifier: { scheme: 'username', value: opts.credentials.username },
|
|
248
|
+
credential: { scheme: 'basic', value: opts.credentials.password },
|
|
249
|
+
});
|
|
207
250
|
},
|
|
208
251
|
});
|
|
252
|
+
|
|
253
|
+
this.component(AuthenticateComponent);
|
|
254
|
+
|
|
255
|
+
// Register multiple strategies at once
|
|
209
256
|
AuthenticationStrategyRegistry.getInstance().register({
|
|
210
257
|
container: this,
|
|
211
|
-
|
|
212
|
-
|
|
258
|
+
strategies: [
|
|
259
|
+
{ name: Authentication.STRATEGY_JWT, strategy: JWTAuthenticationStrategy },
|
|
260
|
+
{ name: Authentication.STRATEGY_BASIC, strategy: BasicAuthenticationStrategy },
|
|
261
|
+
],
|
|
213
262
|
});
|
|
214
263
|
}
|
|
215
|
-
|
|
216
|
-
preConfigure(): ValueOrPromise<void> {
|
|
217
|
-
// ...
|
|
218
|
-
this.registerAuth();
|
|
219
|
-
// ...
|
|
220
|
-
}
|
|
221
264
|
}
|
|
222
265
|
```
|
|
223
266
|
|
|
224
|
-
#### 2.
|
|
267
|
+
#### 2. Basic Authentication Verification Function
|
|
268
|
+
|
|
269
|
+
The `verifyCredentials` function receives an options object with credentials and context:
|
|
270
|
+
|
|
271
|
+
```typescript
|
|
272
|
+
type TBasicAuthVerifyFn = (opts: {
|
|
273
|
+
credentials: { username: string; password: string };
|
|
274
|
+
context: Context;
|
|
275
|
+
}) => Promise<IAuthUser | null>;
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Example implementation:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
basicOptions: {
|
|
282
|
+
verifyCredentials: async (opts) => {
|
|
283
|
+
const { credentials, context } = opts;
|
|
284
|
+
|
|
285
|
+
// Look up user by username
|
|
286
|
+
const user = await userRepo.findByUsername(credentials.username);
|
|
287
|
+
|
|
288
|
+
if (!user) {
|
|
289
|
+
return null; // User not found
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Verify password
|
|
293
|
+
const isValid = await bcrypt.compare(credentials.password, user.passwordHash);
|
|
294
|
+
|
|
295
|
+
if (!isValid) {
|
|
296
|
+
return null; // Invalid password
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Return user info (must include userId)
|
|
300
|
+
return {
|
|
301
|
+
userId: user.id,
|
|
302
|
+
roles: user.roles,
|
|
303
|
+
// ... any additional fields
|
|
304
|
+
};
|
|
305
|
+
},
|
|
306
|
+
}
|
|
307
|
+
```
|
|
225
308
|
|
|
226
|
-
|
|
309
|
+
#### 3. Implementing an AuthenticationService
|
|
227
310
|
|
|
228
|
-
|
|
311
|
+
The `AuthenticateComponent` depends on a service that implements the `IAuthService` interface.
|
|
229
312
|
|
|
230
313
|
```typescript
|
|
231
314
|
// src/services/authentication.service.ts
|
|
@@ -251,21 +334,16 @@ export class AuthenticationService extends BaseService implements IAuthService {
|
|
|
251
334
|
async signIn(context: Context, opts: TSignInRequest): Promise<{ token: string }> {
|
|
252
335
|
const { identifier, credential } = opts;
|
|
253
336
|
|
|
254
|
-
//
|
|
255
|
-
|
|
256
|
-
// 2. Verify the credential (e.g., check the password).
|
|
257
|
-
// 3. If valid, create a JWT payload.
|
|
258
|
-
const user = { id: 'user-id-from-db', roles: [] }; // Dummy user
|
|
337
|
+
// Your custom logic here
|
|
338
|
+
const user = await this.userRepo.findByIdentifier(identifier);
|
|
259
339
|
|
|
260
|
-
if (
|
|
340
|
+
if (!user || !await this.verifyCredential(credential, user)) {
|
|
261
341
|
throw getError({ message: 'Invalid credentials' });
|
|
262
342
|
}
|
|
263
|
-
// --- End of custom logic ---
|
|
264
343
|
|
|
265
344
|
const payload: IJWTTokenPayload = {
|
|
266
345
|
userId: user.id,
|
|
267
346
|
roles: user.roles,
|
|
268
|
-
// Add any other data you want in the token
|
|
269
347
|
};
|
|
270
348
|
|
|
271
349
|
const token = await this._jwtTokenService.generate({ payload });
|
|
@@ -274,174 +352,110 @@ export class AuthenticationService extends BaseService implements IAuthService {
|
|
|
274
352
|
|
|
275
353
|
async signUp(context: Context, opts: any): Promise<any> {
|
|
276
354
|
// Implement your sign-up logic
|
|
277
|
-
throw getError({ message: 'Method not implemented.' });
|
|
278
355
|
}
|
|
279
356
|
|
|
280
357
|
async changePassword(context: Context, opts: any): Promise<any> {
|
|
281
358
|
// Implement your change password logic
|
|
282
|
-
throw getError({ message: 'Method not implemented.' });
|
|
283
359
|
}
|
|
284
360
|
}
|
|
285
361
|
```
|
|
286
362
|
|
|
287
|
-
|
|
363
|
+
#### 4. Securing Routes
|
|
288
364
|
|
|
289
|
-
|
|
365
|
+
Use `authStrategies` and `authMode` in route configurations:
|
|
290
366
|
|
|
291
|
-
|
|
367
|
+
**Single Strategy:**
|
|
292
368
|
|
|
293
369
|
```typescript
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
signIn: {
|
|
304
|
-
request: {
|
|
305
|
-
schema: z.object({
|
|
306
|
-
email: z.string().email(),
|
|
307
|
-
password: z.string(),
|
|
308
|
-
}),
|
|
309
|
-
},
|
|
310
|
-
response: {
|
|
311
|
-
schema: z.object({
|
|
312
|
-
token: z.string(),
|
|
313
|
-
expiresIn: z.number(),
|
|
314
|
-
}),
|
|
315
|
-
},
|
|
316
|
-
},
|
|
317
|
-
signUp: {
|
|
318
|
-
request: {
|
|
319
|
-
schema: z.object({
|
|
320
|
-
email: z.string().email(),
|
|
321
|
-
password: z.string().min(8),
|
|
322
|
-
firstName: z.string(),
|
|
323
|
-
lastName: z.string(),
|
|
324
|
-
}),
|
|
325
|
-
},
|
|
326
|
-
response: {
|
|
327
|
-
schema: z.object({
|
|
328
|
-
token: z.string(),
|
|
329
|
-
userId: z.string(),
|
|
330
|
-
}),
|
|
331
|
-
},
|
|
332
|
-
},
|
|
333
|
-
changePassword: {
|
|
334
|
-
request: {
|
|
335
|
-
schema: z.object({
|
|
336
|
-
currentPassword: z.string(),
|
|
337
|
-
newPassword: z.string().min(8),
|
|
338
|
-
}),
|
|
339
|
-
},
|
|
340
|
-
response: {
|
|
341
|
-
schema: z.object({
|
|
342
|
-
success: z.boolean(),
|
|
343
|
-
message: z.string().optional(),
|
|
344
|
-
}),
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
});
|
|
370
|
+
const SECURE_ROUTE_CONFIG = {
|
|
371
|
+
path: '/secure-data',
|
|
372
|
+
method: HTTP.Methods.GET,
|
|
373
|
+
authStrategies: [Authentication.STRATEGY_JWT],
|
|
374
|
+
responses: jsonResponse({
|
|
375
|
+
description: 'Protected data',
|
|
376
|
+
schema: z.object({ message: z.string() }),
|
|
377
|
+
}),
|
|
378
|
+
} as const;
|
|
349
379
|
```
|
|
350
380
|
|
|
351
|
-
|
|
381
|
+
**Multiple Strategies with Fallback (any mode):**
|
|
352
382
|
|
|
353
383
|
```typescript
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// Register your custom controller
|
|
366
|
-
this.controller(CustomAuthController);
|
|
367
|
-
|
|
368
|
-
AuthenticationStrategyRegistry.getInstance().register({
|
|
369
|
-
container: this,
|
|
370
|
-
name: Authentication.STRATEGY_JWT,
|
|
371
|
-
strategy: JWTAuthenticationStrategy,
|
|
372
|
-
});
|
|
373
|
-
}
|
|
374
|
-
}
|
|
384
|
+
const FALLBACK_AUTH_CONFIG = {
|
|
385
|
+
path: '/api/data',
|
|
386
|
+
method: HTTP.Methods.GET,
|
|
387
|
+
authStrategies: [Authentication.STRATEGY_JWT, Authentication.STRATEGY_BASIC],
|
|
388
|
+
authMode: 'any', // First successful strategy wins (default)
|
|
389
|
+
responses: jsonResponse({
|
|
390
|
+
description: 'Data accessible via JWT or Basic auth',
|
|
391
|
+
schema: z.object({ data: z.any() }),
|
|
392
|
+
}),
|
|
393
|
+
} as const;
|
|
375
394
|
```
|
|
376
395
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
In your controllers, use decorator-based routing (`@get`, `@post`, etc.) with the `authStrategies` property in the `configs` object to protect endpoints. This will automatically run the necessary authentication middlewares and attach the authenticated user to the Hono `Context`, which can then be accessed type-safely using `TRouteContext`.
|
|
396
|
+
**Multiple Strategies with MFA (all mode):**
|
|
380
397
|
|
|
381
398
|
```typescript
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
get, // Or @api, @post, etc.
|
|
388
|
-
HTTP,
|
|
389
|
-
jsonResponse,
|
|
390
|
-
IJWTTokenPayload,
|
|
391
|
-
TRouteContext, // Import TRouteContext for type safety
|
|
392
|
-
} from '@venizia/ignis';
|
|
393
|
-
import { z } from '@hono/zod-openapi';
|
|
394
|
-
|
|
395
|
-
const SECURE_ROUTE_CONFIG = {
|
|
396
|
-
path: '/secure-data',
|
|
397
|
-
method: HTTP.Methods.GET,
|
|
398
|
-
authStrategies: [Authentication.STRATEGY_JWT],
|
|
399
|
+
const MFA_CONFIG = {
|
|
400
|
+
path: '/admin/sensitive',
|
|
401
|
+
method: HTTP.Methods.POST,
|
|
402
|
+
authStrategies: [Authentication.STRATEGY_JWT, Authentication.STRATEGY_MFA],
|
|
403
|
+
authMode: 'all', // All strategies must pass
|
|
399
404
|
responses: jsonResponse({
|
|
400
|
-
|
|
401
|
-
|
|
405
|
+
description: 'Requires both JWT and MFA',
|
|
406
|
+
schema: z.object({ success: z.boolean() }),
|
|
402
407
|
}),
|
|
403
408
|
} as const;
|
|
409
|
+
```
|
|
404
410
|
|
|
405
|
-
|
|
406
|
-
export class TestController extends BaseController {
|
|
407
|
-
constructor() {
|
|
408
|
-
super({
|
|
409
|
-
scope: TestController.name,
|
|
410
|
-
path: '/test',
|
|
411
|
-
});
|
|
412
|
-
}
|
|
411
|
+
**Skipping Authentication:**
|
|
413
412
|
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
)
|
|
422
|
-
}
|
|
423
|
-
}
|
|
413
|
+
```typescript
|
|
414
|
+
const PUBLIC_ROUTE_CONFIG = {
|
|
415
|
+
path: '/public',
|
|
416
|
+
method: HTTP.Methods.GET,
|
|
417
|
+
skipAuth: true, // Bypass authentication even if controller has default auth
|
|
418
|
+
responses: jsonResponse({
|
|
419
|
+
description: 'Public endpoint',
|
|
420
|
+
schema: z.object({ message: z.string() }),
|
|
421
|
+
}),
|
|
422
|
+
} as const;
|
|
424
423
|
```
|
|
425
424
|
|
|
426
425
|
#### 5. Accessing the Current User in Context
|
|
427
426
|
|
|
428
|
-
After
|
|
427
|
+
After authentication, the user payload is available on the Hono `Context`:
|
|
429
428
|
|
|
430
429
|
```typescript
|
|
431
430
|
import { Context } from 'hono';
|
|
432
431
|
import { Authentication, IJWTTokenPayload } from '@venizia/ignis';
|
|
433
432
|
|
|
434
|
-
// Inside a route handler
|
|
433
|
+
// Inside a route handler
|
|
435
434
|
const user = c.get(Authentication.CURRENT_USER) as IJWTTokenPayload | undefined;
|
|
436
435
|
|
|
437
436
|
if (user) {
|
|
438
437
|
console.log('Authenticated user ID:', user.userId);
|
|
439
|
-
|
|
440
|
-
} else {
|
|
441
|
-
console.log('User is not authenticated or not found in context.');
|
|
438
|
+
console.log('User roles:', user.roles);
|
|
442
439
|
}
|
|
443
440
|
```
|
|
444
441
|
|
|
442
|
+
#### 6. Dynamic Skip Authentication
|
|
443
|
+
|
|
444
|
+
Use `Authentication.SKIP_AUTHENTICATION` to dynamically skip auth in middleware:
|
|
445
|
+
|
|
446
|
+
```typescript
|
|
447
|
+
import { Authentication } from '@venizia/ignis';
|
|
448
|
+
import { createMiddleware } from 'hono/factory';
|
|
449
|
+
|
|
450
|
+
const conditionalAuthMiddleware = createMiddleware(async (c, next) => {
|
|
451
|
+
// Skip auth for certain conditions
|
|
452
|
+
if (c.req.header('X-API-Key') === 'valid-api-key') {
|
|
453
|
+
c.set(Authentication.SKIP_AUTHENTICATION, true);
|
|
454
|
+
}
|
|
455
|
+
return next();
|
|
456
|
+
});
|
|
457
|
+
```
|
|
458
|
+
|
|
445
459
|
## See Also
|
|
446
460
|
|
|
447
461
|
- **Related Concepts:**
|
|
@@ -33,4 +33,4 @@ Reusable classes and functions providing common functionality - designed for eas
|
|
|
33
33
|
- [Components](/references/components/) - Framework components
|
|
34
34
|
|
|
35
35
|
- **Best Practices:**
|
|
36
|
-
- [Code Style Standards](/best-practices/code-style-standards) - Helper usage patterns
|
|
36
|
+
- [Code Style Standards](/best-practices/code-style-standards/) - Helper usage patterns
|