digitaltwin-core 0.14.3 → 1.0.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 (123) hide show
  1. package/README.md +135 -0
  2. package/dist/auth/apisix_parser.d.ts +46 -51
  3. package/dist/auth/apisix_parser.d.ts.map +1 -1
  4. package/dist/auth/apisix_parser.js +62 -86
  5. package/dist/auth/apisix_parser.js.map +1 -1
  6. package/dist/auth/auth_provider.d.ts +118 -0
  7. package/dist/auth/auth_provider.d.ts.map +1 -0
  8. package/dist/auth/auth_provider.js +8 -0
  9. package/dist/auth/auth_provider.js.map +1 -0
  10. package/dist/auth/auth_provider_factory.d.ts +91 -0
  11. package/dist/auth/auth_provider_factory.d.ts.map +1 -0
  12. package/dist/auth/auth_provider_factory.js +146 -0
  13. package/dist/auth/auth_provider_factory.js.map +1 -0
  14. package/dist/auth/index.d.ts +3 -0
  15. package/dist/auth/index.d.ts.map +1 -1
  16. package/dist/auth/index.js +3 -0
  17. package/dist/auth/index.js.map +1 -1
  18. package/dist/auth/providers/gateway_auth_provider.d.ts +78 -0
  19. package/dist/auth/providers/gateway_auth_provider.d.ts.map +1 -0
  20. package/dist/auth/providers/gateway_auth_provider.js +109 -0
  21. package/dist/auth/providers/gateway_auth_provider.js.map +1 -0
  22. package/dist/auth/providers/index.d.ts +4 -0
  23. package/dist/auth/providers/index.d.ts.map +1 -0
  24. package/dist/auth/providers/index.js +4 -0
  25. package/dist/auth/providers/index.js.map +1 -0
  26. package/dist/auth/providers/jwt_auth_provider.d.ts +91 -0
  27. package/dist/auth/providers/jwt_auth_provider.d.ts.map +1 -0
  28. package/dist/auth/providers/jwt_auth_provider.js +204 -0
  29. package/dist/auth/providers/jwt_auth_provider.js.map +1 -0
  30. package/dist/auth/providers/no_auth_provider.d.ts +61 -0
  31. package/dist/auth/providers/no_auth_provider.d.ts.map +1 -0
  32. package/dist/auth/providers/no_auth_provider.js +76 -0
  33. package/dist/auth/providers/no_auth_provider.js.map +1 -0
  34. package/dist/components/assets_manager.d.ts +1 -1
  35. package/dist/components/assets_manager.d.ts.map +1 -1
  36. package/dist/components/assets_manager.js +52 -44
  37. package/dist/components/assets_manager.js.map +1 -1
  38. package/dist/components/collector.d.ts.map +1 -1
  39. package/dist/components/collector.js +30 -18
  40. package/dist/components/collector.js.map +1 -1
  41. package/dist/components/custom_table_manager.d.ts.map +1 -1
  42. package/dist/components/custom_table_manager.js +36 -65
  43. package/dist/components/custom_table_manager.js.map +1 -1
  44. package/dist/components/harvester.d.ts.map +1 -1
  45. package/dist/components/harvester.js +46 -33
  46. package/dist/components/harvester.js.map +1 -1
  47. package/dist/components/tileset_manager.d.ts.map +1 -1
  48. package/dist/components/tileset_manager.js +20 -15
  49. package/dist/components/tileset_manager.js.map +1 -1
  50. package/dist/database/adapters/knex_database_adapter.d.ts +5 -0
  51. package/dist/database/adapters/knex_database_adapter.d.ts.map +1 -1
  52. package/dist/database/adapters/knex_database_adapter.js +116 -34
  53. package/dist/database/adapters/knex_database_adapter.js.map +1 -1
  54. package/dist/database/database_adapter.d.ts +11 -0
  55. package/dist/database/database_adapter.d.ts.map +1 -1
  56. package/dist/database/database_adapter.js.map +1 -1
  57. package/dist/engine/digital_twin_engine.d.ts +48 -6
  58. package/dist/engine/digital_twin_engine.d.ts.map +1 -1
  59. package/dist/engine/digital_twin_engine.js +175 -48
  60. package/dist/engine/digital_twin_engine.js.map +1 -1
  61. package/dist/engine/endpoints.d.ts.map +1 -1
  62. package/dist/engine/endpoints.js +35 -3
  63. package/dist/engine/endpoints.js.map +1 -1
  64. package/dist/engine/error_handler.d.ts +20 -0
  65. package/dist/engine/error_handler.d.ts.map +1 -0
  66. package/dist/engine/error_handler.js +69 -0
  67. package/dist/engine/error_handler.js.map +1 -0
  68. package/dist/engine/health.d.ts +112 -0
  69. package/dist/engine/health.d.ts.map +1 -0
  70. package/dist/engine/health.js +190 -0
  71. package/dist/engine/health.js.map +1 -0
  72. package/dist/engine/scheduler.d.ts.map +1 -1
  73. package/dist/engine/scheduler.js +9 -1
  74. package/dist/engine/scheduler.js.map +1 -1
  75. package/dist/engine/upload_processor.d.ts.map +1 -1
  76. package/dist/engine/upload_processor.js +24 -12
  77. package/dist/engine/upload_processor.js.map +1 -1
  78. package/dist/errors/index.d.ts +94 -0
  79. package/dist/errors/index.d.ts.map +1 -0
  80. package/dist/errors/index.js +149 -0
  81. package/dist/errors/index.js.map +1 -0
  82. package/dist/index.d.ts +6 -0
  83. package/dist/index.d.ts.map +1 -1
  84. package/dist/index.js +8 -0
  85. package/dist/index.js.map +1 -1
  86. package/dist/storage/adapters/local_storage_service.d.ts +6 -0
  87. package/dist/storage/adapters/local_storage_service.d.ts.map +1 -1
  88. package/dist/storage/adapters/local_storage_service.js +26 -4
  89. package/dist/storage/adapters/local_storage_service.js.map +1 -1
  90. package/dist/storage/adapters/ovh_storage_service.d.ts.map +1 -1
  91. package/dist/storage/adapters/ovh_storage_service.js +5 -6
  92. package/dist/storage/adapters/ovh_storage_service.js.map +1 -1
  93. package/dist/storage/storage_factory.d.ts.map +1 -1
  94. package/dist/storage/storage_factory.js +4 -1
  95. package/dist/storage/storage_factory.js.map +1 -1
  96. package/dist/storage/storage_service.d.ts.map +1 -1
  97. package/dist/storage/storage_service.js +6 -2
  98. package/dist/storage/storage_service.js.map +1 -1
  99. package/dist/utils/graceful_shutdown.d.ts +44 -0
  100. package/dist/utils/graceful_shutdown.d.ts.map +1 -0
  101. package/dist/utils/graceful_shutdown.js +79 -0
  102. package/dist/utils/graceful_shutdown.js.map +1 -0
  103. package/dist/utils/http_responses.d.ts +20 -0
  104. package/dist/utils/http_responses.d.ts.map +1 -1
  105. package/dist/utils/http_responses.js +28 -2
  106. package/dist/utils/http_responses.js.map +1 -1
  107. package/dist/utils/safe_async.d.ts +50 -0
  108. package/dist/utils/safe_async.d.ts.map +1 -0
  109. package/dist/utils/safe_async.js +90 -0
  110. package/dist/utils/safe_async.js.map +1 -0
  111. package/dist/validation/index.d.ts +3 -0
  112. package/dist/validation/index.d.ts.map +1 -0
  113. package/dist/validation/index.js +7 -0
  114. package/dist/validation/index.js.map +1 -0
  115. package/dist/validation/schemas.d.ts +273 -0
  116. package/dist/validation/schemas.d.ts.map +1 -0
  117. package/dist/validation/schemas.js +82 -0
  118. package/dist/validation/schemas.js.map +1 -0
  119. package/dist/validation/validate.d.ts +49 -0
  120. package/dist/validation/validate.d.ts.map +1 -0
  121. package/dist/validation/validate.js +110 -0
  122. package/dist/validation/validate.js.map +1 -0
  123. package/package.json +9 -1
package/README.md CHANGED
@@ -12,6 +12,7 @@ Digital Twin Core is a minimalist TypeScript framework used to collect and proce
12
12
  - **Storage adapters** – currently local filesystem and OVH Object Storage via S3 API.
13
13
  - **Database adapter** – implemented with [Knex](https://knexjs.org/) to index metadata.
14
14
  - **Engine** – orchestrates components, schedules jobs with BullMQ and exposes endpoints via Express.
15
+ - **Authentication** – pluggable authentication system supporting API gateway headers, JWT tokens, or no-auth mode.
15
16
 
16
17
  ## Installation
17
18
 
@@ -326,6 +327,139 @@ class WMSLayersManager extends CustomTableManager {
326
327
  - `update(id, data)` - Update existing record
327
328
  - `delete(id)` - Delete record
328
329
 
330
+ ## Request Validation
331
+
332
+ The framework includes VineJS integration for type-safe request validation:
333
+
334
+ ```typescript
335
+ import { validate, AssetUploadSchema } from 'digitaltwin-core'
336
+
337
+ // In your component
338
+ async handleUpload(req: any) {
339
+ const data = await validate(AssetUploadSchema, req.body)
340
+ // data is now typed and validated
341
+ }
342
+ ```
343
+
344
+ Validation errors return HTTP 422 with detailed error messages:
345
+
346
+ ```json
347
+ {
348
+ "error": "Validation failed",
349
+ "details": [
350
+ { "field": "description", "message": "The description field must be a string" }
351
+ ]
352
+ }
353
+ ```
354
+
355
+ ## Error Handling
356
+
357
+ The framework provides custom error classes for structured error handling:
358
+
359
+ ```typescript
360
+ import { CollectorError, ValidationError, StorageError } from 'digitaltwin-core'
361
+
362
+ // Errors include context
363
+ throw new CollectorError('Failed to fetch data', 'weather-collector', originalError)
364
+
365
+ // Validation errors return 422
366
+ throw new ValidationError('Invalid input', details)
367
+ ```
368
+
369
+ All component errors are caught and logged with context (component name, stack trace). Non-critical operations use `safeAsync` to log errors without crashing:
370
+
371
+ ```typescript
372
+ import { safeAsync } from 'digitaltwin-core'
373
+
374
+ // Won't throw, just logs on failure
375
+ await safeAsync(() => cleanup(), 'cleanup temporary files', logger)
376
+ ```
377
+
378
+ ## Production Features
379
+
380
+ ### Graceful Shutdown
381
+
382
+ The engine supports graceful shutdown with configurable timeout:
383
+
384
+ ```typescript
385
+ const engine = new DigitalTwinEngine({ database, storage })
386
+
387
+ // Configure shutdown timeout (default: 30s)
388
+ engine.setShutdownTimeout(60000)
389
+
390
+ // Check if shutting down
391
+ if (engine.isShuttingDown()) {
392
+ // Don't accept new work
393
+ }
394
+
395
+ // Graceful stop
396
+ await engine.stop()
397
+ ```
398
+
399
+ ### Health Checks
400
+
401
+ Register custom health checks for monitoring:
402
+
403
+ ```typescript
404
+ engine.registerHealthCheck('external-api', async () => {
405
+ const response = await fetch('https://api.example.com/health')
406
+ return { status: response.ok ? 'up' : 'down' }
407
+ })
408
+
409
+ // Built-in checks: database, redis (if configured)
410
+ const names = engine.getHealthCheckNames() // ['database', 'redis', 'external-api']
411
+
412
+ // Remove check
413
+ engine.removeHealthCheck('external-api')
414
+ ```
415
+
416
+ ### OpenAPI Specification
417
+
418
+ Generate OpenAPI 3.0 specs from your components:
419
+
420
+ ```typescript
421
+ import { OpenAPIGenerator } from 'digitaltwin-core'
422
+
423
+ const spec = OpenAPIGenerator.generate({
424
+ info: { title: 'My API', version: '1.0.0' },
425
+ components: [collector, assetsManager, handler]
426
+ })
427
+
428
+ // Output as JSON or YAML
429
+ const json = OpenAPIGenerator.toJSON(spec)
430
+ const yaml = OpenAPIGenerator.toYAML(spec)
431
+ ```
432
+
433
+ ## Authentication
434
+
435
+ The framework supports multiple authentication modes:
436
+
437
+ - **Gateway** (default): Uses headers from API gateways (Apache APISIX, KrakenD)
438
+ - **JWT**: Direct JWT token validation
439
+ - **None**: Disabled for development/testing
440
+
441
+ ### Gateway Mode (Default)
442
+
443
+ No configuration needed. The framework reads `x-user-id` and `x-user-roles` headers set by your API gateway.
444
+
445
+ ### JWT Mode
446
+
447
+ ```bash
448
+ export AUTH_MODE=jwt
449
+ export JWT_SECRET=your-secret-key
450
+ # Or for RSA: JWT_PUBLIC_KEY or JWT_PUBLIC_KEY_FILE
451
+ ```
452
+
453
+ ### Disable Authentication
454
+
455
+ ```bash
456
+ export DIGITALTWIN_DISABLE_AUTH=true
457
+ # Or
458
+ export AUTH_MODE=none
459
+ ```
460
+
461
+ For detailed configuration options, see [src/auth/README.md](src/auth/README.md).
462
+
329
463
  ## Project Scaffolding
330
464
 
331
465
  Use [create-digitaltwin](https://github.com/CePseudoBE/create-digitaltwin) to quickly bootstrap new projects:
@@ -348,6 +482,7 @@ node dt make:harvester DataProcessor --source weather-collector
348
482
  ## Folder structure
349
483
 
350
484
  - `src/` – framework sources
485
+ - `auth/` – authentication providers and user management
351
486
  - `components/` – base classes for collectors, harvesters, handlers and assets manager
352
487
  - `engine/` – orchestration logic
353
488
  - `storage/` – storage service abstractions and adapters
@@ -1,16 +1,19 @@
1
1
  import type { AuthenticatedUser } from './types.js';
2
+ import type { AuthProvider } from './auth_provider.js';
2
3
  /**
3
4
  * Parses authentication information from Apache APISIX headers set after Keycloak authentication.
4
5
  *
5
- * This class handles the parsing of authentication headers forwarded by Apache APISIX
6
- * after successful Keycloak authentication. APISIX acts as a gateway that:
7
- * 1. Validates JWT tokens with Keycloak
8
- * 2. Extracts user information from the token
9
- * 3. Forwards user data as HTTP headers to downstream services
6
+ * This class provides a static API for backward compatibility while internally using
7
+ * the AuthProvider system. It automatically handles:
8
+ * - Gateway mode (x-user-id, x-user-roles headers)
9
+ * - JWT mode (Authorization: Bearer token)
10
+ * - No-auth mode (DIGITALTWIN_DISABLE_AUTH=true)
10
11
  *
11
- * Authentication can be disabled via environment variables for development/testing:
12
- * - Set DIGITALTWIN_DISABLE_AUTH=true to bypass authentication checks
13
- * - Set DIGITALTWIN_ANONYMOUS_USER_ID=custom-id to use a custom anonymous user ID
12
+ * For new code, consider using AuthProviderFactory directly:
13
+ * ```typescript
14
+ * const authProvider = AuthProviderFactory.fromEnv()
15
+ * const user = authProvider.parseRequest(req)
16
+ * ```
14
17
  *
15
18
  * @example
16
19
  * ```typescript
@@ -24,18 +27,38 @@ import type { AuthenticatedUser } from './types.js';
24
27
  * ```
25
28
  */
26
29
  export declare class ApisixAuthParser {
30
+ private static _provider;
31
+ /**
32
+ * Get the authentication provider instance.
33
+ * Creates it on first use based on environment configuration.
34
+ */
35
+ private static getProvider;
36
+ /**
37
+ * Reset the provider instance (useful for testing).
38
+ * @internal
39
+ */
40
+ static _resetProvider(): void;
41
+ /**
42
+ * Set a custom provider (useful for testing).
43
+ * @internal
44
+ */
45
+ static _setProvider(provider: AuthProvider): void;
46
+ /**
47
+ * Create a request-like object from headers for the AuthProvider.
48
+ */
49
+ private static toAuthRequest;
27
50
  /**
28
- * Extracts user information from APISIX headers.
51
+ * Extracts user information from authentication headers.
29
52
  *
30
- * Parses the authentication headers forwarded by APISIX:
31
- * - `x-user-id`: Keycloak user UUID (required)
32
- * - `x-user-roles`: Comma-separated list of user roles (optional)
53
+ * Parses the authentication headers (gateway mode) or JWT token (jwt mode):
54
+ * - Gateway: `x-user-id` and `x-user-roles` headers
55
+ * - JWT: `Authorization: Bearer <token>` header
33
56
  *
34
57
  * When authentication is disabled (DIGITALTWIN_DISABLE_AUTH=true),
35
- * returns a default anonymous user instead of requiring headers.
58
+ * returns a default anonymous user.
36
59
  *
37
- * @param headers - HTTP request headers from APISIX
38
- * @returns Parsed user authentication data, or null if x-user-id is missing and auth is enabled
60
+ * @param headers - HTTP request headers
61
+ * @returns Parsed user authentication data, or null if not authenticated
39
62
  *
40
63
  * @example
41
64
  * ```typescript
@@ -46,48 +69,35 @@ export declare class ApisixAuthParser {
46
69
  *
47
70
  * const authUser = ApisixAuthParser.parseAuthHeaders(headers)
48
71
  * // Returns: { id: '6e06a527...', roles: ['default-roles-master', 'offline_access'] }
49
- *
50
- * // With DIGITALTWIN_DISABLE_AUTH=true
51
- * const authUser = ApisixAuthParser.parseAuthHeaders({})
52
- * // Returns: { id: 'anonymous', roles: ['anonymous'] }
53
72
  * ```
54
73
  */
55
74
  static parseAuthHeaders(headers: Record<string, string>): AuthenticatedUser | null;
56
75
  /**
57
- * Checks if a request has valid authentication headers.
76
+ * Checks if a request has valid authentication.
58
77
  *
59
78
  * Performs a quick validation to determine if the request contains
60
- * the minimum required authentication information (x-user-id header).
61
- * Use this for early authentication checks before parsing.
79
+ * valid authentication credentials (gateway headers or JWT token).
62
80
  *
63
- * When authentication is disabled (DIGITALTWIN_DISABLE_AUTH=true),
64
- * this always returns true to allow all requests through.
81
+ * When authentication is disabled, this always returns true.
65
82
  *
66
83
  * @param headers - HTTP request headers
67
- * @returns true if x-user-id header is present or auth is disabled, false otherwise
84
+ * @returns true if authentication is valid or disabled, false otherwise
68
85
  *
69
86
  * @example
70
87
  * ```typescript
71
- * // Early authentication check in handler
72
88
  * if (!ApisixAuthParser.hasValidAuth(req.headers)) {
73
89
  * return { status: 401, content: 'Authentication required' }
74
90
  * }
75
- *
76
- * // Now safe to proceed with parsing
77
- * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
78
91
  * ```
79
92
  */
80
93
  static hasValidAuth(headers: Record<string, string>): boolean;
81
94
  /**
82
95
  * Extracts just the user ID from headers.
83
96
  *
84
- * Convenience method for cases where you only need the user ID
85
- * without parsing the full authentication context.
86
- *
87
- * When authentication is disabled, returns the configured anonymous user ID.
97
+ * Convenience method for cases where you only need the user ID.
88
98
  *
89
99
  * @param headers - HTTP request headers
90
- * @returns Keycloak user ID, anonymous user ID if auth disabled, or null if not present
100
+ * @returns User ID, or null if not authenticated
91
101
  *
92
102
  * @example
93
103
  * ```typescript
@@ -101,13 +111,8 @@ export declare class ApisixAuthParser {
101
111
  /**
102
112
  * Extracts just the user roles from headers.
103
113
  *
104
- * Convenience method for cases where you only need the user roles
105
- * without parsing the full authentication context.
106
- *
107
- * When authentication is disabled, returns the anonymous user roles.
108
- *
109
114
  * @param headers - HTTP request headers
110
- * @returns Array of role names, anonymous roles if auth disabled, empty array if no roles header present
115
+ * @returns Array of role names, empty array if not authenticated
111
116
  *
112
117
  * @example
113
118
  * ```typescript
@@ -121,24 +126,14 @@ export declare class ApisixAuthParser {
121
126
  /**
122
127
  * Checks if a user has the admin role.
123
128
  *
124
- * Determines if the authenticated user has administrative privileges by checking
125
- * if their roles include the configured admin role name (default: "admin").
126
- *
127
- * The admin role name can be configured via DIGITALTWIN_ADMIN_ROLE_NAME environment variable.
128
- *
129
129
  * @param headers - HTTP request headers
130
130
  * @returns true if user has admin role, false otherwise
131
131
  *
132
132
  * @example
133
133
  * ```typescript
134
134
  * if (ApisixAuthParser.isAdmin(req.headers)) {
135
- * // User has full administrative access
136
- * // Can view all assets including private assets owned by others
137
- * console.log('Admin user detected')
135
+ * // Admin-only logic
138
136
  * }
139
- *
140
- * // With custom admin role name (DIGITALTWIN_ADMIN_ROLE_NAME=administrator)
141
- * const isAdmin = ApisixAuthParser.isAdmin(req.headers)
142
137
  * ```
143
138
  */
144
139
  static isAdmin(headers: Record<string, string>): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"apisix_parser.d.ts","sourceRoot":"","sources":["../../src/auth/apisix_parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAGnD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,gBAAgB;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,iBAAiB,GAAG,IAAI;IAqBlF;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO;IAS7D;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI;IAShE;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE;IAU9D;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO;CAK3D"}
1
+ {"version":3,"file":"apisix_parser.d.ts","sourceRoot":"","sources":["../../src/auth/apisix_parser.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AACnD,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,oBAAoB,CAAA;AAGnE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,gBAAgB;IACzB,OAAO,CAAC,MAAM,CAAC,SAAS,CAA4B;IAEpD;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,WAAW;IAO1B;;;OAGG;IACH,MAAM,CAAC,cAAc,IAAI,IAAI;IAI7B;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAIjD;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;IAI5B;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,iBAAiB,GAAG,IAAI;IAIlF;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO;IAI7D;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI;IAIhE;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE;IAI9D;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO;CAG3D"}
@@ -1,16 +1,18 @@
1
- import { AuthConfig } from './auth_config.js';
1
+ import { AuthProviderFactory } from './auth_provider_factory.js';
2
2
  /**
3
3
  * Parses authentication information from Apache APISIX headers set after Keycloak authentication.
4
4
  *
5
- * This class handles the parsing of authentication headers forwarded by Apache APISIX
6
- * after successful Keycloak authentication. APISIX acts as a gateway that:
7
- * 1. Validates JWT tokens with Keycloak
8
- * 2. Extracts user information from the token
9
- * 3. Forwards user data as HTTP headers to downstream services
5
+ * This class provides a static API for backward compatibility while internally using
6
+ * the AuthProvider system. It automatically handles:
7
+ * - Gateway mode (x-user-id, x-user-roles headers)
8
+ * - JWT mode (Authorization: Bearer token)
9
+ * - No-auth mode (DIGITALTWIN_DISABLE_AUTH=true)
10
10
  *
11
- * Authentication can be disabled via environment variables for development/testing:
12
- * - Set DIGITALTWIN_DISABLE_AUTH=true to bypass authentication checks
13
- * - Set DIGITALTWIN_ANONYMOUS_USER_ID=custom-id to use a custom anonymous user ID
11
+ * For new code, consider using AuthProviderFactory directly:
12
+ * ```typescript
13
+ * const authProvider = AuthProviderFactory.fromEnv()
14
+ * const user = authProvider.parseRequest(req)
15
+ * ```
14
16
  *
15
17
  * @example
16
18
  * ```typescript
@@ -24,18 +26,49 @@ import { AuthConfig } from './auth_config.js';
24
26
  * ```
25
27
  */
26
28
  export class ApisixAuthParser {
29
+ static { this._provider = null; }
30
+ /**
31
+ * Get the authentication provider instance.
32
+ * Creates it on first use based on environment configuration.
33
+ */
34
+ static getProvider() {
35
+ if (!this._provider) {
36
+ this._provider = AuthProviderFactory.fromEnv();
37
+ }
38
+ return this._provider;
39
+ }
40
+ /**
41
+ * Reset the provider instance (useful for testing).
42
+ * @internal
43
+ */
44
+ static _resetProvider() {
45
+ this._provider = null;
46
+ }
47
+ /**
48
+ * Set a custom provider (useful for testing).
49
+ * @internal
50
+ */
51
+ static _setProvider(provider) {
52
+ this._provider = provider;
53
+ }
54
+ /**
55
+ * Create a request-like object from headers for the AuthProvider.
56
+ */
57
+ static toAuthRequest(headers) {
58
+ return { headers };
59
+ }
27
60
  /**
28
- * Extracts user information from APISIX headers.
61
+ * Extracts user information from authentication headers.
29
62
  *
30
- * Parses the authentication headers forwarded by APISIX:
31
- * - `x-user-id`: Keycloak user UUID (required)
32
- * - `x-user-roles`: Comma-separated list of user roles (optional)
63
+ * Parses the authentication headers (gateway mode) or JWT token (jwt mode):
64
+ * - Gateway: `x-user-id` and `x-user-roles` headers
65
+ * - JWT: `Authorization: Bearer <token>` header
33
66
  *
34
67
  * When authentication is disabled (DIGITALTWIN_DISABLE_AUTH=true),
35
- * returns a default anonymous user instead of requiring headers.
68
+ * returns a default anonymous user.
36
69
  *
37
- * @param headers - HTTP request headers from APISIX
38
- * @returns Parsed user authentication data, or null if x-user-id is missing and auth is enabled
70
+ * @param headers - HTTP request headers
71
+ * @returns Parsed user authentication data, or null if not authenticated
39
72
  *
40
73
  * @example
41
74
  * ```typescript
@@ -46,70 +79,39 @@ export class ApisixAuthParser {
46
79
  *
47
80
  * const authUser = ApisixAuthParser.parseAuthHeaders(headers)
48
81
  * // Returns: { id: '6e06a527...', roles: ['default-roles-master', 'offline_access'] }
49
- *
50
- * // With DIGITALTWIN_DISABLE_AUTH=true
51
- * const authUser = ApisixAuthParser.parseAuthHeaders({})
52
- * // Returns: { id: 'anonymous', roles: ['anonymous'] }
53
82
  * ```
54
83
  */
55
84
  static parseAuthHeaders(headers) {
56
- // If authentication is disabled, return anonymous user
57
- if (AuthConfig.isAuthDisabled()) {
58
- return AuthConfig.getAnonymousUser();
59
- }
60
- const userId = headers['x-user-id'];
61
- if (!userId) {
62
- return null;
63
- }
64
- // Parse roles from comma-separated string
65
- const rolesString = headers['x-user-roles'] || '';
66
- const roles = rolesString ? rolesString.split(',').map(role => role.trim()) : [];
67
- return {
68
- id: userId,
69
- roles: roles
70
- };
85
+ return this.getProvider().parseRequest(this.toAuthRequest(headers));
71
86
  }
72
87
  /**
73
- * Checks if a request has valid authentication headers.
88
+ * Checks if a request has valid authentication.
74
89
  *
75
90
  * Performs a quick validation to determine if the request contains
76
- * the minimum required authentication information (x-user-id header).
77
- * Use this for early authentication checks before parsing.
91
+ * valid authentication credentials (gateway headers or JWT token).
78
92
  *
79
- * When authentication is disabled (DIGITALTWIN_DISABLE_AUTH=true),
80
- * this always returns true to allow all requests through.
93
+ * When authentication is disabled, this always returns true.
81
94
  *
82
95
  * @param headers - HTTP request headers
83
- * @returns true if x-user-id header is present or auth is disabled, false otherwise
96
+ * @returns true if authentication is valid or disabled, false otherwise
84
97
  *
85
98
  * @example
86
99
  * ```typescript
87
- * // Early authentication check in handler
88
100
  * if (!ApisixAuthParser.hasValidAuth(req.headers)) {
89
101
  * return { status: 401, content: 'Authentication required' }
90
102
  * }
91
- *
92
- * // Now safe to proceed with parsing
93
- * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
94
103
  * ```
95
104
  */
96
105
  static hasValidAuth(headers) {
97
- // If authentication is disabled, all requests are valid
98
- if (AuthConfig.isAuthDisabled()) {
99
- return true;
100
- }
101
- return !!headers['x-user-id'];
106
+ return this.getProvider().hasValidAuth(this.toAuthRequest(headers));
102
107
  }
103
108
  /**
104
109
  * Extracts just the user ID from headers.
105
110
  *
106
- * Convenience method for cases where you only need the user ID
107
- * without parsing the full authentication context.
108
- *
109
- * When authentication is disabled, returns the configured anonymous user ID.
111
+ * Convenience method for cases where you only need the user ID.
110
112
  *
111
113
  * @param headers - HTTP request headers
112
- * @returns Keycloak user ID, anonymous user ID if auth disabled, or null if not present
114
+ * @returns User ID, or null if not authenticated
113
115
  *
114
116
  * @example
115
117
  * ```typescript
@@ -120,22 +122,13 @@ export class ApisixAuthParser {
120
122
  * ```
121
123
  */
122
124
  static getUserId(headers) {
123
- // If authentication is disabled, return anonymous user ID
124
- if (AuthConfig.isAuthDisabled()) {
125
- return AuthConfig.getAnonymousUserId();
126
- }
127
- return headers['x-user-id'] || null;
125
+ return this.getProvider().getUserId(this.toAuthRequest(headers));
128
126
  }
129
127
  /**
130
128
  * Extracts just the user roles from headers.
131
129
  *
132
- * Convenience method for cases where you only need the user roles
133
- * without parsing the full authentication context.
134
- *
135
- * When authentication is disabled, returns the anonymous user roles.
136
- *
137
130
  * @param headers - HTTP request headers
138
- * @returns Array of role names, anonymous roles if auth disabled, empty array if no roles header present
131
+ * @returns Array of role names, empty array if not authenticated
139
132
  *
140
133
  * @example
141
134
  * ```typescript
@@ -146,40 +139,23 @@ export class ApisixAuthParser {
146
139
  * ```
147
140
  */
148
141
  static getUserRoles(headers) {
149
- // If authentication is disabled, return anonymous user roles
150
- if (AuthConfig.isAuthDisabled()) {
151
- return AuthConfig.getAnonymousUser().roles;
152
- }
153
- const rolesString = headers['x-user-roles'] || '';
154
- return rolesString ? rolesString.split(',').map(role => role.trim()) : [];
142
+ return this.getProvider().getUserRoles(this.toAuthRequest(headers));
155
143
  }
156
144
  /**
157
145
  * Checks if a user has the admin role.
158
146
  *
159
- * Determines if the authenticated user has administrative privileges by checking
160
- * if their roles include the configured admin role name (default: "admin").
161
- *
162
- * The admin role name can be configured via DIGITALTWIN_ADMIN_ROLE_NAME environment variable.
163
- *
164
147
  * @param headers - HTTP request headers
165
148
  * @returns true if user has admin role, false otherwise
166
149
  *
167
150
  * @example
168
151
  * ```typescript
169
152
  * if (ApisixAuthParser.isAdmin(req.headers)) {
170
- * // User has full administrative access
171
- * // Can view all assets including private assets owned by others
172
- * console.log('Admin user detected')
153
+ * // Admin-only logic
173
154
  * }
174
- *
175
- * // With custom admin role name (DIGITALTWIN_ADMIN_ROLE_NAME=administrator)
176
- * const isAdmin = ApisixAuthParser.isAdmin(req.headers)
177
155
  * ```
178
156
  */
179
157
  static isAdmin(headers) {
180
- const roles = this.getUserRoles(headers);
181
- const adminRoleName = AuthConfig.getAdminRoleName();
182
- return roles.includes(adminRoleName);
158
+ return this.getProvider().isAdmin(this.toAuthRequest(headers));
183
159
  }
184
160
  }
185
161
  //# sourceMappingURL=apisix_parser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"apisix_parser.js","sourceRoot":"","sources":["../../src/auth/apisix_parser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAE7C;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,OAAO,gBAAgB;IACzB;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2BG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAA+B;QACnD,uDAAuD;QACvD,IAAI,UAAU,CAAC,cAAc,EAAE,EAAE,CAAC;YAC9B,OAAO,UAAU,CAAC,gBAAgB,EAAE,CAAA;QACxC,CAAC;QAED,MAAM,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;QACnC,IAAI,CAAC,MAAM,EAAE,CAAC;YACV,OAAO,IAAI,CAAA;QACf,CAAC;QAED,0CAA0C;QAC1C,MAAM,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;QACjD,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;QAEhF,OAAO;YACH,EAAE,EAAE,MAAM;YACV,KAAK,EAAE,KAAK;SACf,CAAA;IACL,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,YAAY,CAAC,OAA+B;QAC/C,wDAAwD;QACxD,IAAI,UAAU,CAAC,cAAc,EAAE,EAAE,CAAC;YAC9B,OAAO,IAAI,CAAA;QACf,CAAC;QAED,OAAO,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IACjC,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,SAAS,CAAC,OAA+B;QAC5C,0DAA0D;QAC1D,IAAI,UAAU,CAAC,cAAc,EAAE,EAAE,CAAC;YAC9B,OAAO,UAAU,CAAC,kBAAkB,EAAE,CAAA;QAC1C,CAAC;QAED,OAAO,OAAO,CAAC,WAAW,CAAC,IAAI,IAAI,CAAA;IACvC,CAAC;IAED;;;;;;;;;;;;;;;;;;OAkBG;IACH,MAAM,CAAC,YAAY,CAAC,OAA+B;QAC/C,6DAA6D;QAC7D,IAAI,UAAU,CAAC,cAAc,EAAE,EAAE,CAAC;YAC9B,OAAO,UAAU,CAAC,gBAAgB,EAAE,CAAC,KAAK,CAAA;QAC9C,CAAC;QAED,MAAM,WAAW,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,EAAE,CAAA;QACjD,OAAO,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAA;IAC7E,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,OAAO,CAAC,OAA+B;QAC1C,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,CAAA;QACxC,MAAM,aAAa,GAAG,UAAU,CAAC,gBAAgB,EAAE,CAAA;QACnD,OAAO,KAAK,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAA;IACxC,CAAC;CACJ"}
1
+ {"version":3,"file":"apisix_parser.js","sourceRoot":"","sources":["../../src/auth/apisix_parser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAEhE;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,OAAO,gBAAgB;aACV,cAAS,GAAwB,IAAI,CAAA;IAEpD;;;OAGG;IACK,MAAM,CAAC,WAAW;QACtB,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC;YAClB,IAAI,CAAC,SAAS,GAAG,mBAAmB,CAAC,OAAO,EAAE,CAAA;QAClD,CAAC;QACD,OAAO,IAAI,CAAC,SAAS,CAAA;IACzB,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,cAAc;QACjB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;IACzB,CAAC;IAED;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,QAAsB;QACtC,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAA;IAC7B,CAAC;IAED;;OAEG;IACK,MAAM,CAAC,aAAa,CAAC,OAA+B;QACxD,OAAO,EAAE,OAAO,EAAE,CAAA;IACtB,CAAC;IAED;;;;;;;;;;;;;;;;;;;;;;;OAuBG;IACH,MAAM,CAAC,gBAAgB,CAAC,OAA+B;QACnD,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;IACvE,CAAC;IAED;;;;;;;;;;;;;;;;;OAiBG;IACH,MAAM,CAAC,YAAY,CAAC,OAA+B;QAC/C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;IACvE,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,MAAM,CAAC,SAAS,CAAC,OAA+B;QAC5C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;IACpE,CAAC;IAED;;;;;;;;;;;;;OAaG;IACH,MAAM,CAAC,YAAY,CAAC,OAA+B;QAC/C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;IACvE,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,OAAO,CAAC,OAA+B;QAC1C,OAAO,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;IAClE,CAAC"}