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.
- package/README.md +135 -0
- package/dist/auth/apisix_parser.d.ts +46 -51
- package/dist/auth/apisix_parser.d.ts.map +1 -1
- package/dist/auth/apisix_parser.js +62 -86
- package/dist/auth/apisix_parser.js.map +1 -1
- package/dist/auth/auth_provider.d.ts +118 -0
- package/dist/auth/auth_provider.d.ts.map +1 -0
- package/dist/auth/auth_provider.js +8 -0
- package/dist/auth/auth_provider.js.map +1 -0
- package/dist/auth/auth_provider_factory.d.ts +91 -0
- package/dist/auth/auth_provider_factory.d.ts.map +1 -0
- package/dist/auth/auth_provider_factory.js +146 -0
- package/dist/auth/auth_provider_factory.js.map +1 -0
- package/dist/auth/index.d.ts +3 -0
- package/dist/auth/index.d.ts.map +1 -1
- package/dist/auth/index.js +3 -0
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/providers/gateway_auth_provider.d.ts +78 -0
- package/dist/auth/providers/gateway_auth_provider.d.ts.map +1 -0
- package/dist/auth/providers/gateway_auth_provider.js +109 -0
- package/dist/auth/providers/gateway_auth_provider.js.map +1 -0
- package/dist/auth/providers/index.d.ts +4 -0
- package/dist/auth/providers/index.d.ts.map +1 -0
- package/dist/auth/providers/index.js +4 -0
- package/dist/auth/providers/index.js.map +1 -0
- package/dist/auth/providers/jwt_auth_provider.d.ts +91 -0
- package/dist/auth/providers/jwt_auth_provider.d.ts.map +1 -0
- package/dist/auth/providers/jwt_auth_provider.js +204 -0
- package/dist/auth/providers/jwt_auth_provider.js.map +1 -0
- package/dist/auth/providers/no_auth_provider.d.ts +61 -0
- package/dist/auth/providers/no_auth_provider.d.ts.map +1 -0
- package/dist/auth/providers/no_auth_provider.js +76 -0
- package/dist/auth/providers/no_auth_provider.js.map +1 -0
- package/dist/components/assets_manager.d.ts +1 -1
- package/dist/components/assets_manager.d.ts.map +1 -1
- package/dist/components/assets_manager.js +52 -44
- package/dist/components/assets_manager.js.map +1 -1
- package/dist/components/collector.d.ts.map +1 -1
- package/dist/components/collector.js +30 -18
- package/dist/components/collector.js.map +1 -1
- package/dist/components/custom_table_manager.d.ts.map +1 -1
- package/dist/components/custom_table_manager.js +36 -65
- package/dist/components/custom_table_manager.js.map +1 -1
- package/dist/components/harvester.d.ts.map +1 -1
- package/dist/components/harvester.js +46 -33
- package/dist/components/harvester.js.map +1 -1
- package/dist/components/tileset_manager.d.ts.map +1 -1
- package/dist/components/tileset_manager.js +20 -15
- package/dist/components/tileset_manager.js.map +1 -1
- package/dist/database/adapters/knex_database_adapter.d.ts +5 -0
- package/dist/database/adapters/knex_database_adapter.d.ts.map +1 -1
- package/dist/database/adapters/knex_database_adapter.js +116 -34
- package/dist/database/adapters/knex_database_adapter.js.map +1 -1
- package/dist/database/database_adapter.d.ts +11 -0
- package/dist/database/database_adapter.d.ts.map +1 -1
- package/dist/database/database_adapter.js.map +1 -1
- package/dist/engine/digital_twin_engine.d.ts +48 -6
- package/dist/engine/digital_twin_engine.d.ts.map +1 -1
- package/dist/engine/digital_twin_engine.js +175 -48
- package/dist/engine/digital_twin_engine.js.map +1 -1
- package/dist/engine/endpoints.d.ts.map +1 -1
- package/dist/engine/endpoints.js +35 -3
- package/dist/engine/endpoints.js.map +1 -1
- package/dist/engine/error_handler.d.ts +20 -0
- package/dist/engine/error_handler.d.ts.map +1 -0
- package/dist/engine/error_handler.js +69 -0
- package/dist/engine/error_handler.js.map +1 -0
- package/dist/engine/health.d.ts +112 -0
- package/dist/engine/health.d.ts.map +1 -0
- package/dist/engine/health.js +190 -0
- package/dist/engine/health.js.map +1 -0
- package/dist/engine/scheduler.d.ts.map +1 -1
- package/dist/engine/scheduler.js +9 -1
- package/dist/engine/scheduler.js.map +1 -1
- package/dist/engine/upload_processor.d.ts.map +1 -1
- package/dist/engine/upload_processor.js +24 -12
- package/dist/engine/upload_processor.js.map +1 -1
- package/dist/errors/index.d.ts +94 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +149 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/storage/adapters/local_storage_service.d.ts +6 -0
- package/dist/storage/adapters/local_storage_service.d.ts.map +1 -1
- package/dist/storage/adapters/local_storage_service.js +26 -4
- package/dist/storage/adapters/local_storage_service.js.map +1 -1
- package/dist/storage/adapters/ovh_storage_service.d.ts.map +1 -1
- package/dist/storage/adapters/ovh_storage_service.js +5 -6
- package/dist/storage/adapters/ovh_storage_service.js.map +1 -1
- package/dist/storage/storage_factory.d.ts.map +1 -1
- package/dist/storage/storage_factory.js +4 -1
- package/dist/storage/storage_factory.js.map +1 -1
- package/dist/storage/storage_service.d.ts.map +1 -1
- package/dist/storage/storage_service.js +6 -2
- package/dist/storage/storage_service.js.map +1 -1
- package/dist/utils/graceful_shutdown.d.ts +44 -0
- package/dist/utils/graceful_shutdown.d.ts.map +1 -0
- package/dist/utils/graceful_shutdown.js +79 -0
- package/dist/utils/graceful_shutdown.js.map +1 -0
- package/dist/utils/http_responses.d.ts +20 -0
- package/dist/utils/http_responses.d.ts.map +1 -1
- package/dist/utils/http_responses.js +28 -2
- package/dist/utils/http_responses.js.map +1 -1
- package/dist/utils/safe_async.d.ts +50 -0
- package/dist/utils/safe_async.d.ts.map +1 -0
- package/dist/utils/safe_async.js +90 -0
- package/dist/utils/safe_async.js.map +1 -0
- package/dist/validation/index.d.ts +3 -0
- package/dist/validation/index.d.ts.map +1 -0
- package/dist/validation/index.js +7 -0
- package/dist/validation/index.js.map +1 -0
- package/dist/validation/schemas.d.ts +273 -0
- package/dist/validation/schemas.d.ts.map +1 -0
- package/dist/validation/schemas.js +82 -0
- package/dist/validation/schemas.js.map +1 -0
- package/dist/validation/validate.d.ts +49 -0
- package/dist/validation/validate.d.ts.map +1 -0
- package/dist/validation/validate.js +110 -0
- package/dist/validation/validate.js.map +1 -0
- 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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
|
51
|
+
* Extracts user information from authentication headers.
|
|
29
52
|
*
|
|
30
|
-
* Parses the authentication headers
|
|
31
|
-
* - `x-user-id
|
|
32
|
-
* - `
|
|
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
|
|
58
|
+
* returns a default anonymous user.
|
|
36
59
|
*
|
|
37
|
-
* @param headers - HTTP request headers
|
|
38
|
-
* @returns Parsed user authentication data, or null if
|
|
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
|
|
76
|
+
* Checks if a request has valid authentication.
|
|
58
77
|
*
|
|
59
78
|
* Performs a quick validation to determine if the request contains
|
|
60
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
* //
|
|
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;
|
|
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 {
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
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
|
|
61
|
+
* Extracts user information from authentication headers.
|
|
29
62
|
*
|
|
30
|
-
* Parses the authentication headers
|
|
31
|
-
* - `x-user-id
|
|
32
|
-
* - `
|
|
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
|
|
68
|
+
* returns a default anonymous user.
|
|
36
69
|
*
|
|
37
|
-
* @param headers - HTTP request headers
|
|
38
|
-
* @returns Parsed user authentication data, or null if
|
|
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
|
-
|
|
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
|
|
88
|
+
* Checks if a request has valid authentication.
|
|
74
89
|
*
|
|
75
90
|
* Performs a quick validation to determine if the request contains
|
|
76
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
* //
|
|
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
|
-
|
|
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":"
|
|
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"}
|