@navios/core 0.4.0 → 0.5.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.
Files changed (92) hide show
  1. package/README.md +95 -2
  2. package/docs/README.md +310 -3
  3. package/docs/adapters.md +308 -0
  4. package/docs/application-setup.md +524 -0
  5. package/docs/attributes.md +689 -0
  6. package/docs/controllers.md +373 -0
  7. package/docs/endpoints.md +444 -0
  8. package/docs/exceptions.md +316 -0
  9. package/docs/guards.md +550 -0
  10. package/docs/modules.md +251 -0
  11. package/docs/quick-start.md +295 -0
  12. package/docs/services.md +428 -0
  13. package/docs/testing.md +704 -0
  14. package/lib/_tsup-dts-rollup.d.mts +300 -235
  15. package/lib/_tsup-dts-rollup.d.ts +300 -235
  16. package/lib/index.d.mts +47 -26
  17. package/lib/index.d.ts +47 -26
  18. package/lib/index.js +633 -1072
  19. package/lib/index.js.map +1 -1
  20. package/lib/index.mjs +631 -1064
  21. package/lib/index.mjs.map +1 -1
  22. package/package.json +4 -7
  23. package/project.json +9 -1
  24. package/src/__tests__/config.service.spec.mts +11 -9
  25. package/src/__tests__/controller.spec.mts +0 -1
  26. package/src/config/config.service.mts +2 -2
  27. package/src/decorators/controller.decorator.mts +1 -1
  28. package/src/decorators/endpoint.decorator.mts +2 -2
  29. package/src/decorators/header.decorator.mts +1 -1
  30. package/src/decorators/multipart.decorator.mts +1 -1
  31. package/src/decorators/stream.decorator.mts +2 -3
  32. package/src/factories/endpoint-adapter.factory.mts +21 -0
  33. package/src/factories/http-adapter.factory.mts +20 -0
  34. package/src/factories/index.mts +6 -0
  35. package/src/factories/multipart-adapter.factory.mts +21 -0
  36. package/src/factories/reply.factory.mts +21 -0
  37. package/src/factories/request.factory.mts +21 -0
  38. package/src/factories/stream-adapter.factory.mts +20 -0
  39. package/src/index.mts +1 -1
  40. package/src/interfaces/abstract-execution-context.inteface.mts +13 -0
  41. package/src/interfaces/abstract-http-adapter.interface.mts +20 -0
  42. package/src/interfaces/abstract-http-cors-options.interface.mts +59 -0
  43. package/src/interfaces/abstract-http-handler-adapter.interface.mts +13 -0
  44. package/src/interfaces/abstract-http-listen-options.interface.mts +4 -0
  45. package/src/interfaces/can-activate.mts +4 -2
  46. package/src/interfaces/http-header.mts +18 -0
  47. package/src/interfaces/index.mts +6 -0
  48. package/src/logger/console-logger.service.mts +28 -44
  49. package/src/logger/index.mts +1 -2
  50. package/src/logger/logger.service.mts +9 -128
  51. package/src/logger/logger.tokens.mts +21 -0
  52. package/src/metadata/handler.metadata.mts +7 -5
  53. package/src/navios.application.mts +65 -172
  54. package/src/navios.environment.mts +30 -0
  55. package/src/navios.factory.mts +53 -12
  56. package/src/services/guard-runner.service.mts +19 -9
  57. package/src/services/index.mts +0 -2
  58. package/src/services/module-loader.service.mts +4 -3
  59. package/src/tokens/endpoint-adapter.token.mts +8 -0
  60. package/src/tokens/execution-context.token.mts +2 -2
  61. package/src/tokens/http-adapter.token.mts +8 -0
  62. package/src/tokens/index.mts +4 -1
  63. package/src/tokens/multipart-adapter.token.mts +8 -0
  64. package/src/tokens/reply.token.mts +1 -5
  65. package/src/tokens/request.token.mts +1 -7
  66. package/src/tokens/stream-adapter.token.mts +8 -0
  67. package/docs/recipes/prisma.md +0 -60
  68. package/e2e/endpoints/get.spec.mts +0 -97
  69. package/e2e/endpoints/post.spec.mts +0 -113
  70. package/examples/simple-test/api/index.mts +0 -64
  71. package/examples/simple-test/config/config.service.mts +0 -14
  72. package/examples/simple-test/config/configuration.mts +0 -7
  73. package/examples/simple-test/index.mts +0 -16
  74. package/examples/simple-test/src/acl/acl-modern.guard.mts +0 -15
  75. package/examples/simple-test/src/acl/acl.guard.mts +0 -14
  76. package/examples/simple-test/src/acl/app.guard.mts +0 -27
  77. package/examples/simple-test/src/acl/one-more.guard.mts +0 -15
  78. package/examples/simple-test/src/acl/public.attribute.mts +0 -21
  79. package/examples/simple-test/src/app.module.mts +0 -9
  80. package/examples/simple-test/src/user/user.controller.mts +0 -72
  81. package/examples/simple-test/src/user/user.module.mts +0 -14
  82. package/examples/simple-test/src/user/user.service.mts +0 -14
  83. package/src/adapters/endpoint-adapter.service.mts +0 -72
  84. package/src/adapters/handler-adapter.interface.mts +0 -21
  85. package/src/adapters/index.mts +0 -4
  86. package/src/adapters/multipart-adapter.service.mts +0 -135
  87. package/src/adapters/stream-adapter.service.mts +0 -91
  88. package/src/logger/logger.factory.mts +0 -36
  89. package/src/logger/pino-wrapper.mts +0 -64
  90. package/src/services/controller-adapter.service.mts +0 -124
  91. package/src/services/execution-context.mts +0 -54
  92. package/src/tokens/application.token.mts +0 -9
package/docs/guards.md ADDED
@@ -0,0 +1,550 @@
1
+ # Guards
2
+
3
+ Guards in Navios are classes that determine whether a request should be allowed to proceed to the endpoint handler. They implement authentication, authorization, rate limiting, and other security or business logic checks.
4
+
5
+ ## What are Guards?
6
+
7
+ Guards are executed before the endpoint handler and can:
8
+
9
+ - Allow or deny access to endpoints
10
+ - Perform authentication checks
11
+ - Validate user permissions
12
+ - Implement rate limiting
13
+ - Execute any custom logic before request processing
14
+
15
+ Guards receive an `AbstractExecutionContext` parameter that provides access to:
16
+
17
+ - Request and response objects via `getRequest()` and `getReply()`
18
+ - Module metadata via `getModule()`
19
+ - Controller metadata via `getController()`
20
+ - Handler/endpoint metadata via `getHandler()`
21
+ - All attributes defined on modules, controllers, and handlers
22
+
23
+ ## Execution Context Interface
24
+
25
+ The `AbstractExecutionContext` interface provides the following methods:
26
+
27
+ ```typescript
28
+ interface AbstractExecutionContext {
29
+ getModule(): ModuleMetadata // Module-level metadata and attributes
30
+ getController(): ControllerMetadata // Controller-level metadata and attributes
31
+ getHandler(): HandlerMetadata // Handler/endpoint-level metadata and attributes
32
+ getRequest(): any // Framework-specific request object
33
+ getReply(): any // Framework-specific response object
34
+ }
35
+ ```
36
+
37
+ ## Creating Guards
38
+
39
+ ### Basic Guard
40
+
41
+ ```typescript
42
+ import type { AbstractExecutionContext, CanActivate } from '@navios/core'
43
+
44
+ import { Injectable } from '@navios/di'
45
+
46
+ @Injectable()
47
+ export class AuthGuard implements CanActivate {
48
+ async canActivate(
49
+ executionContext: AbstractExecutionContext,
50
+ ): Promise<boolean> {
51
+ const request = executionContext.getRequest()
52
+ const authHeader = request.headers?.authorization
53
+
54
+ if (!authHeader) {
55
+ return false
56
+ }
57
+
58
+ // Validate token
59
+ const token = authHeader.replace('Bearer ', '')
60
+ return this.validateToken(token)
61
+ }
62
+
63
+ private async validateToken(token: string): Promise<boolean> {
64
+ // Token validation logic
65
+ return token === 'valid-token'
66
+ }
67
+ }
68
+ ```
69
+
70
+ ### Guard with Dependencies
71
+
72
+ ```typescript
73
+ import type { AbstractExecutionContext, CanActivate } from '@navios/core'
74
+
75
+ import { inject, Injectable, Logger } from '@navios/di'
76
+
77
+ @Injectable()
78
+ export class JwtAuthGuard implements CanActivate {
79
+ private jwtService = inject(JwtService)
80
+ private userService = inject(UserService)
81
+ private logger = inject(Logger, { context: 'JwtAuthGuard' })
82
+
83
+ async canActivate(
84
+ executionContext: AbstractExecutionContext,
85
+ ): Promise<boolean> {
86
+ try {
87
+ const request = executionContext.getRequest()
88
+ const token = this.extractTokenFromHeader(request.headers)
89
+
90
+ if (!token) {
91
+ this.logger.debug('No token provided')
92
+ return false
93
+ }
94
+
95
+ const payload = await this.jwtService.verify(token)
96
+ const user = await this.userService.findById(payload.sub)
97
+
98
+ if (!user || !user.isActive) {
99
+ this.logger.debug(`User not found or inactive: ${payload.sub}`)
100
+ return false
101
+ }
102
+
103
+ // Attach user to request context
104
+ request.user = user
105
+ return true
106
+ } catch (error) {
107
+ this.logger.debug('Token validation failed', error.message)
108
+ return false
109
+ }
110
+ }
111
+
112
+ private extractTokenFromHeader(
113
+ headers: Record<string, string>,
114
+ ): string | null {
115
+ const authHeader = headers?.authorization
116
+ if (!authHeader || !authHeader.startsWith('Bearer ')) {
117
+ return null
118
+ }
119
+ return authHeader.substring(7)
120
+ }
121
+ }
122
+ ```
123
+
124
+ ## Using Guards
125
+
126
+ ### Controller-Level Guards
127
+
128
+ Guards applied to controllers affect all endpoints in that controller:
129
+
130
+ ```typescript
131
+ import { Controller, UseGuards } from '@navios/core'
132
+
133
+ @Controller({
134
+ guards: [AuthGuard], // Applied to all endpoints
135
+ })
136
+ export class UserController {
137
+ // All endpoints here require authentication
138
+ }
139
+ ```
140
+
141
+ ### Endpoint-Level Guards
142
+
143
+ Guards applied to specific endpoints:
144
+
145
+ ```typescript
146
+ @Controller()
147
+ export class UserController {
148
+ @Endpoint(profileEndpoint)
149
+ async getProfile() {
150
+ // No authentication required
151
+ }
152
+
153
+ @UseGuards([AuthGuard, AdminGuard])
154
+ @Endpoint(deleteUserEndpoint)
155
+ async deleteUser() {
156
+ // Requires authentication AND admin role
157
+ }
158
+ }
159
+ ```
160
+
161
+ ### Module-Level Guards
162
+
163
+ Guards applied to modules affect all controllers in that module:
164
+
165
+ ```typescript
166
+ import { Module } from '@navios/core'
167
+
168
+ @Module({
169
+ guards: [AuthGuard], // Applied to all controllers in module
170
+ controllers: [UserController, PostController],
171
+ })
172
+ export class ProtectedModule {}
173
+ ```
174
+
175
+ ## Guard Execution Order
176
+
177
+ Guards are executed in the following order:
178
+
179
+ 1. Module-level guards
180
+ 2. Controller-level guards
181
+ 3. Endpoint-level guards
182
+
183
+ ```typescript
184
+ @Module({
185
+ guards: [AuthGuard], // 1st
186
+ controllers: [UserController],
187
+ })
188
+ export class AppModule {}
189
+
190
+ @Controller({
191
+ guards: [RoleGuard], // 2nd
192
+ })
193
+ export class UserController {
194
+ @UseGuards([OwnershipGuard]) // 3rd
195
+ @Endpoint(deleteUserEndpoint)
196
+ async deleteUser() {
197
+ // Execution order: AuthGuard -> RoleGuard -> OwnershipGuard
198
+ }
199
+ }
200
+ ```
201
+
202
+ ## Common Guard Patterns
203
+
204
+ ### Role-Based Authorization
205
+
206
+ Use AttributeFactory to define roles required for endpoints
207
+
208
+ ### Resource Ownership Guard
209
+
210
+ ### Rate Limiting Guard
211
+
212
+ ### API Key Guard
213
+
214
+ ```typescript
215
+ import type { AbstractExecutionContext, CanActivate } from '@navios/core'
216
+
217
+ import { inject, Injectable, Logger } from '@navios/di'
218
+
219
+ @Injectable()
220
+ export class ApiKeyGuard implements CanActivate {
221
+ private configService = inject(ConfigService)
222
+ private logger = inject(Logger, { context: 'ApiKeyGuard' })
223
+
224
+ async canActivate(
225
+ executionContext: AbstractExecutionContext,
226
+ ): Promise<boolean> {
227
+ const request = executionContext.getRequest()
228
+ const apiKey = request.headers?.['x-api-key']
229
+
230
+ if (!apiKey) {
231
+ this.logger.debug('No API key provided')
232
+ return false
233
+ }
234
+
235
+ const validApiKeys = this.configService.get<string[]>('VALID_API_KEYS')
236
+ const isValid = validApiKeys.includes(apiKey)
237
+
238
+ if (!isValid) {
239
+ this.logger.warn(`Invalid API key used: ${apiKey.substring(0, 8)}...`)
240
+ }
241
+
242
+ return isValid
243
+ }
244
+ }
245
+
246
+ @Controller()
247
+ export class ApiController {
248
+ @UseGuards([ApiKeyGuard])
249
+ @Endpoint(publicDataEndpoint)
250
+ async getPublicData() {
251
+ // Requires valid API key
252
+ }
253
+ }
254
+ ```
255
+
256
+ ## Advanced Guard Patterns
257
+
258
+ ### Conditional Guards
259
+
260
+ ```typescript
261
+ import type { AbstractExecutionContext, CanActivate } from '@navios/core'
262
+
263
+ import { inject, Injectable } from '@navios/di'
264
+
265
+ @Injectable()
266
+ export class ConditionalAuthGuard implements CanActivate {
267
+ private configService = inject(ConfigService)
268
+ private authGuard = inject(AuthGuard)
269
+
270
+ async canActivate(
271
+ executionContext: AbstractExecutionContext,
272
+ ): Promise<boolean> {
273
+ const authRequired = this.configService.get<boolean>('AUTH_REQUIRED')
274
+
275
+ if (!authRequired) {
276
+ return true
277
+ }
278
+
279
+ return this.authGuard.canActivate(executionContext)
280
+ }
281
+ }
282
+ ```
283
+
284
+ ### Guards with Attributes
285
+
286
+ Guards can read attributes from the execution context using `AttributeFactory`:
287
+
288
+ ```typescript
289
+ import type { AbstractExecutionContext, CanActivate } from '@navios/core'
290
+
291
+ import { AttributeFactory, inject, Injectable, Logger } from '@navios/di'
292
+
293
+ // Define attributes
294
+ const PublicSymbol = Symbol.for('Public')
295
+ export const Public = AttributeFactory.createAttribute(PublicSymbol)
296
+
297
+ const RolesSymbol = Symbol.for('Roles')
298
+ const RolesSchema = z.object({
299
+ roles: z.array(z.string()),
300
+ })
301
+ export const Roles = AttributeFactory.createAttribute(RolesSymbol, RolesSchema)
302
+
303
+ @Injectable()
304
+ export class AuthGuard implements CanActivate {
305
+ private logger = inject(Logger, { context: 'AuthGuard' })
306
+
307
+ async canActivate(
308
+ executionContext: AbstractExecutionContext,
309
+ ): Promise<boolean> {
310
+ // Check if endpoint is marked as public using AttributeFactory.getLast
311
+ // This checks module, controller, and handler metadata in hierarchy order
312
+ const isPublic = AttributeFactory.getLast(Public, [
313
+ executionContext.getModule(),
314
+ executionContext.getController(),
315
+ executionContext.getHandler(),
316
+ ])
317
+
318
+ if (isPublic) {
319
+ this.logger.debug('Public endpoint, skipping authentication')
320
+ return true
321
+ }
322
+
323
+ // Check required roles
324
+ const requiredRoles = AttributeFactory.getLast(Roles, [
325
+ executionContext.getModule(),
326
+ executionContext.getController(),
327
+ executionContext.getHandler(),
328
+ ])
329
+
330
+ const request = executionContext.getRequest()
331
+ const user = request.user
332
+
333
+ if (!user) {
334
+ return false
335
+ }
336
+
337
+ if (!requiredRoles) {
338
+ return true // No specific roles required, just authentication
339
+ }
340
+
341
+ // Check if user has any of the required roles
342
+ return requiredRoles.roles.some((role) => user.roles.includes(role))
343
+ }
344
+ }
345
+
346
+ // Usage examples
347
+ @Controller()
348
+ export class UserController {
349
+ @Public() // This endpoint is public
350
+ @Endpoint(loginEndpoint)
351
+ async login() {
352
+ // Public endpoint
353
+ }
354
+
355
+ @Roles({ roles: ['admin', 'moderator'] }) // Requires admin or moderator role
356
+ @Endpoint(deleteUserEndpoint)
357
+ async deleteUser() {
358
+ // Requires authentication and admin/moderator role
359
+ }
360
+ }
361
+ ```
362
+
363
+ You can also check attributes individually:
364
+
365
+ ```typescript
366
+ @Injectable()
367
+ export class RoleBasedGuard implements CanActivate {
368
+ async canActivate(
369
+ executionContext: AbstractExecutionContext,
370
+ ): Promise<boolean> {
371
+ const handlerMetadata = executionContext.getHandler()
372
+ const controllerMetadata = executionContext.getController()
373
+ const moduleMetadata = executionContext.getModule()
374
+
375
+ // Check if handler has specific attribute
376
+ const handlerRoles = AttributeFactory.get(Roles, handlerMetadata)
377
+ if (handlerRoles) {
378
+ return this.checkRoles(handlerRoles.roles, executionContext)
379
+ }
380
+
381
+ // Fall back to controller-level roles
382
+ const controllerRoles = AttributeFactory.get(Roles, controllerMetadata)
383
+ if (controllerRoles) {
384
+ return this.checkRoles(controllerRoles.roles, executionContext)
385
+ }
386
+
387
+ // Fall back to module-level roles
388
+ const moduleRoles = AttributeFactory.get(Roles, moduleMetadata)
389
+ if (moduleRoles) {
390
+ return this.checkRoles(moduleRoles.roles, executionContext)
391
+ }
392
+
393
+ return true // No role restrictions
394
+ }
395
+
396
+ private checkRoles(
397
+ requiredRoles: string[],
398
+ executionContext: AbstractExecutionContext,
399
+ ): boolean {
400
+ const request = executionContext.getRequest()
401
+ const user = request.user
402
+ return user && requiredRoles.some((role) => user.roles.includes(role))
403
+ }
404
+ }
405
+ ```
406
+
407
+ ## Error Handling in Guards
408
+
409
+ Guards should handle errors gracefully:
410
+
411
+ ```typescript
412
+ import type { AbstractExecutionContext, CanActivate } from '@navios/core'
413
+
414
+ import { inject, Injectable, Logger } from '@navios/di'
415
+
416
+ @Injectable()
417
+ export class SafeAuthGuard implements CanActivate {
418
+ private jwtService = inject(JwtService)
419
+ private logger = inject(Logger, { context: 'SafeAuthGuard' })
420
+
421
+ async canActivate(
422
+ executionContext: AbstractExecutionContext,
423
+ ): Promise<boolean> {
424
+ try {
425
+ const request = executionContext.getRequest()
426
+ const token = this.extractToken(request.headers)
427
+
428
+ if (!token) {
429
+ return false
430
+ }
431
+
432
+ await this.jwtService.verify(token)
433
+ return true
434
+ } catch (error) {
435
+ if (error.name === 'TokenExpiredError') {
436
+ this.logger.debug('Token expired')
437
+ } else if (error.name === 'JsonWebTokenError') {
438
+ this.logger.debug('Invalid token')
439
+ } else {
440
+ this.logger.error('Unexpected error in auth guard', error.stack)
441
+ }
442
+
443
+ return false
444
+ }
445
+ }
446
+
447
+ private extractToken(headers: Record<string, string>): string | null {
448
+ const authHeader = headers?.authorization
449
+ return authHeader?.startsWith('Bearer ') ? authHeader.substring(7) : null
450
+ }
451
+ }
452
+ ```
453
+
454
+ ## Best Practices
455
+
456
+ ### 1. Keep Guards Focused
457
+
458
+ Each guard should have a single responsibility:
459
+
460
+ ```typescript
461
+ // ✅ Good - Single responsibility
462
+ @Injectable()
463
+ export class AuthenticationGuard {
464
+ async canActivate(context: GuardContext): Promise<boolean> {
465
+ // Only handles authentication
466
+ }
467
+ }
468
+
469
+ @Injectable()
470
+ export class AuthorizationGuard {
471
+ async canActivate(context: GuardContext): Promise<boolean> {
472
+ // Only handles authorization
473
+ }
474
+ }
475
+
476
+ // ❌ Avoid - Multiple responsibilities
477
+ @Injectable()
478
+ export class AuthGuard {
479
+ async canActivate(context: GuardContext): Promise<boolean> {
480
+ // Handles both authentication AND authorization
481
+ }
482
+ }
483
+ ```
484
+
485
+ ### 2. Fail Securely
486
+
487
+ When in doubt, deny access:
488
+
489
+ ```typescript
490
+ @Injectable()
491
+ export class SecureGuard {
492
+ async canActivate(context: GuardContext): Promise<boolean> {
493
+ try {
494
+ // Validation logic
495
+ return this.validateAccess(context)
496
+ } catch (error) {
497
+ // Fail securely - deny access on errors
498
+ this.logger.error('Guard validation failed', error.stack)
499
+ return false
500
+ }
501
+ }
502
+ }
503
+ ```
504
+
505
+ ### 3. Use Dependency Injection
506
+
507
+ Inject services rather than creating instances:
508
+
509
+ ```typescript
510
+ // ✅ Good - Use DI
511
+ @Injectable()
512
+ export class AuthGuard implements CanActivate {
513
+ private jwtService = inject(JwtService)
514
+ private userService = inject(UserService)
515
+ }
516
+
517
+ // ❌ Avoid - Direct instantiation
518
+ @Injectable()
519
+ export class AuthGuard implements CanActivate {
520
+ private jwtService = new JwtService()
521
+ private userService = new UserService()
522
+ }
523
+ ```
524
+
525
+ ### 4. Log Security Events
526
+
527
+ Log important security events for monitoring:
528
+
529
+ ```typescript
530
+ @Injectable()
531
+ export class AuthGuard implements CanActivate {
532
+ private logger = inject(Logger, { context: 'AuthGuard' })
533
+
534
+ async canActivate(
535
+ executionContext: AbstractExecutionContext,
536
+ ): Promise<boolean> {
537
+ const request = executionContext.getRequest()
538
+ const result = await this.validateToken(request.headers?.authorization)
539
+
540
+ if (!result) {
541
+ this.logger.warn('Authentication failed', {
542
+ ip: request.ip,
543
+ userAgent: request.headers?.['user-agent'],
544
+ })
545
+ }
546
+
547
+ return result
548
+ }
549
+ }
550
+ ```