digitaltwin-core 0.6.1 → 0.7.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.
Files changed (75) hide show
  1. package/README.md +188 -0
  2. package/dist/auth/apisix_parser.d.ts +122 -0
  3. package/dist/auth/apisix_parser.d.ts.map +1 -0
  4. package/dist/auth/apisix_parser.js +157 -0
  5. package/dist/auth/apisix_parser.js.map +1 -0
  6. package/dist/auth/auth_config.d.ts +95 -0
  7. package/dist/auth/auth_config.d.ts.map +1 -0
  8. package/dist/auth/auth_config.js +125 -0
  9. package/dist/auth/auth_config.js.map +1 -0
  10. package/dist/auth/index.d.ts +5 -0
  11. package/dist/auth/index.d.ts.map +1 -0
  12. package/dist/auth/index.js +4 -0
  13. package/dist/auth/index.js.map +1 -0
  14. package/dist/auth/types.d.ts +100 -0
  15. package/dist/auth/types.d.ts.map +1 -0
  16. package/dist/auth/types.js +2 -0
  17. package/dist/auth/types.js.map +1 -0
  18. package/dist/auth/user_service.d.ts +86 -0
  19. package/dist/auth/user_service.d.ts.map +1 -0
  20. package/dist/auth/user_service.js +239 -0
  21. package/dist/auth/user_service.js.map +1 -0
  22. package/dist/components/assets_manager.d.ts +21 -5
  23. package/dist/components/assets_manager.d.ts.map +1 -1
  24. package/dist/components/assets_manager.js +132 -4
  25. package/dist/components/assets_manager.js.map +1 -1
  26. package/dist/components/custom_table_manager.d.ts +344 -0
  27. package/dist/components/custom_table_manager.d.ts.map +1 -0
  28. package/dist/components/custom_table_manager.js +525 -0
  29. package/dist/components/custom_table_manager.js.map +1 -0
  30. package/dist/components/index.d.ts +4 -1
  31. package/dist/components/index.d.ts.map +1 -1
  32. package/dist/components/index.js +3 -0
  33. package/dist/components/index.js.map +1 -1
  34. package/dist/components/map_manager.d.ts +61 -0
  35. package/dist/components/map_manager.d.ts.map +1 -0
  36. package/dist/components/map_manager.js +233 -0
  37. package/dist/components/map_manager.js.map +1 -0
  38. package/dist/components/tileset_manager.d.ts +55 -0
  39. package/dist/components/tileset_manager.d.ts.map +1 -0
  40. package/dist/components/tileset_manager.js +212 -0
  41. package/dist/components/tileset_manager.js.map +1 -0
  42. package/dist/components/types.d.ts +20 -0
  43. package/dist/components/types.d.ts.map +1 -1
  44. package/dist/database/adapters/knex_database_adapter.d.ts +7 -0
  45. package/dist/database/adapters/knex_database_adapter.d.ts.map +1 -1
  46. package/dist/database/adapters/knex_database_adapter.js +136 -2
  47. package/dist/database/adapters/knex_database_adapter.js.map +1 -1
  48. package/dist/database/database_adapter.d.ts +22 -1
  49. package/dist/database/database_adapter.d.ts.map +1 -1
  50. package/dist/database/database_adapter.js.map +1 -1
  51. package/dist/engine/digital_twin_engine.d.ts +4 -1
  52. package/dist/engine/digital_twin_engine.d.ts.map +1 -1
  53. package/dist/engine/digital_twin_engine.js +69 -3
  54. package/dist/engine/digital_twin_engine.js.map +1 -1
  55. package/dist/engine/endpoints.d.ts +2 -2
  56. package/dist/engine/endpoints.d.ts.map +1 -1
  57. package/dist/engine/endpoints.js.map +1 -1
  58. package/dist/env/env.d.ts +8 -0
  59. package/dist/env/env.d.ts.map +1 -1
  60. package/dist/env/env.js +22 -1
  61. package/dist/env/env.js.map +1 -1
  62. package/dist/index.d.ts +2 -0
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +3 -0
  65. package/dist/index.js.map +1 -1
  66. package/dist/types/data_record.d.ts +1 -1
  67. package/dist/utils/index.d.ts +5 -0
  68. package/dist/utils/index.d.ts.map +1 -0
  69. package/dist/utils/index.js +5 -0
  70. package/dist/utils/index.js.map +1 -0
  71. package/dist/utils/zip_utils.d.ts +24 -0
  72. package/dist/utils/zip_utils.d.ts.map +1 -0
  73. package/dist/utils/zip_utils.js +77 -0
  74. package/dist/utils/zip_utils.js.map +1 -0
  75. package/package.json +3 -1
package/README.md CHANGED
@@ -8,6 +8,7 @@ Digital Twin Core is a minimalist TypeScript framework used to collect and proce
8
8
  - **Harvesters** – transform data collected by collectors, store the results and expose them via GET endpoints.
9
9
  - **Handlers** – expose GET endpoints that directly return the result of the method defined in the decorator.
10
10
  - **Assets Manager** – upload, store and manage file assets with metadata, providing RESTful endpoints for CRUD operations.
11
+ - **Custom Table Manager** – manage structured data in custom database tables with automatic CRUD endpoints and custom business logic endpoints.
11
12
  - **Storage adapters** – currently local filesystem and OVH Object Storage via S3 API.
12
13
  - **Database adapter** – implemented with [Knex](https://knexjs.org/) to index metadata.
13
14
  - **Engine** – orchestrates components, schedules jobs with BullMQ and exposes endpoints via Express.
@@ -138,6 +139,193 @@ class DocumentsManager extends AssetsManager {
138
139
  // Error: "Invalid file extension. Expected: .pdf"
139
140
  ```
140
141
 
142
+ ### Custom Table Manager
143
+
144
+ The Custom Table Manager provides a powerful solution for managing structured data with custom database tables. It automatically generates CRUD endpoints and supports custom business logic endpoints.
145
+
146
+ **Key features:**
147
+ - Custom database table creation with configurable columns and SQL types
148
+ - Automatic CRUD endpoints (GET, POST, PUT, DELETE)
149
+ - Custom business logic endpoints with full request/response control
150
+ - Query validation and field requirements
151
+ - Built-in search and filtering capabilities
152
+ - Support for complex data relationships
153
+
154
+ **Available endpoints (automatic):**
155
+ - `GET /{tableName}` - List all records
156
+ - `POST /{tableName}` - Create new record
157
+ - `GET /{tableName}/{id}` - Get specific record
158
+ - `PUT /{tableName}/{id}` - Update specific record
159
+ - `DELETE /{tableName}/{id}` - Delete specific record
160
+
161
+ **Example usage:**
162
+ ```typescript
163
+ class WMSLayersManager extends CustomTableManager {
164
+ getConfiguration() {
165
+ return {
166
+ name: 'wms_layers',
167
+ description: 'Manage WMS layers for mapping applications',
168
+ columns: {
169
+ 'wms_url': 'text not null',
170
+ 'layer_name': 'text not null',
171
+ 'description': 'text',
172
+ 'active': 'boolean default true',
173
+ 'created_by': 'text',
174
+ 'projection': 'text default "EPSG:4326"'
175
+ },
176
+ // Custom endpoints for business logic
177
+ endpoints: [
178
+ { path: '/add-layers', method: 'post', handler: 'addMultipleLayers' },
179
+ { path: '/activate/:id', method: 'put', handler: 'toggleLayerStatus' },
180
+ { path: '/search', method: 'get', handler: 'searchLayers' },
181
+ { path: '/by-projection/:projection', method: 'get', handler: 'findByProjection' }
182
+ ]
183
+ }
184
+ }
185
+
186
+ // Custom endpoint: Add multiple layers at once
187
+ async addMultipleLayers(req: any): Promise<DataResponse> {
188
+ try {
189
+ const { layers } = req.body
190
+ const results = []
191
+
192
+ for (const layerData of layers) {
193
+ // Use built-in validation
194
+ const id = await this.create({
195
+ wms_url: layerData.url,
196
+ layer_name: layerData.name,
197
+ description: layerData.description || '',
198
+ active: true,
199
+ created_by: layerData.user || 'system'
200
+ })
201
+ results.push({ id, name: layerData.name })
202
+ }
203
+
204
+ return {
205
+ status: 200,
206
+ content: JSON.stringify({
207
+ message: `Successfully added ${results.length} layers`,
208
+ layers: results
209
+ }),
210
+ headers: { 'Content-Type': 'application/json' }
211
+ }
212
+ } catch (error) {
213
+ return {
214
+ status: 400,
215
+ content: JSON.stringify({ error: error.message }),
216
+ headers: { 'Content-Type': 'application/json' }
217
+ }
218
+ }
219
+ }
220
+
221
+ // Custom endpoint: Toggle layer active status
222
+ async toggleLayerStatus(req: any): Promise<DataResponse> {
223
+ try {
224
+ const { id } = req.params
225
+ const layer = await this.findById(parseInt(id))
226
+
227
+ if (!layer) {
228
+ return {
229
+ status: 404,
230
+ content: JSON.stringify({ error: 'Layer not found' }),
231
+ headers: { 'Content-Type': 'application/json' }
232
+ }
233
+ }
234
+
235
+ const newStatus = !layer.active
236
+ await this.update(parseInt(id), { active: newStatus })
237
+
238
+ return {
239
+ status: 200,
240
+ content: JSON.stringify({
241
+ message: `Layer ${newStatus ? 'activated' : 'deactivated'}`,
242
+ layer_id: id,
243
+ active: newStatus
244
+ }),
245
+ headers: { 'Content-Type': 'application/json' }
246
+ }
247
+ } catch (error) {
248
+ return {
249
+ status: 500,
250
+ content: JSON.stringify({ error: error.message }),
251
+ headers: { 'Content-Type': 'application/json' }
252
+ }
253
+ }
254
+ }
255
+
256
+ // Custom endpoint: Advanced search with validation
257
+ async searchLayers(req: any): Promise<DataResponse> {
258
+ try {
259
+ const { query, active_only, projection } = req.query
260
+ const conditions: Record<string, any> = {}
261
+
262
+ if (active_only === 'true') {
263
+ conditions.active = true
264
+ }
265
+
266
+ if (projection) {
267
+ conditions.projection = projection
268
+ }
269
+
270
+ // Use built-in search with validation
271
+ const layers = await this.findByColumns(conditions, {
272
+ validate: (conditions) => {
273
+ if (query && query.length < 3) {
274
+ throw new Error('Search query must be at least 3 characters long')
275
+ }
276
+ }
277
+ })
278
+
279
+ // Filter by text search if provided
280
+ let results = layers
281
+ if (query) {
282
+ results = layers.filter(layer =>
283
+ layer.layer_name.toLowerCase().includes(query.toLowerCase()) ||
284
+ layer.description?.toLowerCase().includes(query.toLowerCase())
285
+ )
286
+ }
287
+
288
+ return {
289
+ status: 200,
290
+ content: JSON.stringify({
291
+ results,
292
+ total: results.length,
293
+ query: { query, active_only, projection }
294
+ }),
295
+ headers: { 'Content-Type': 'application/json' }
296
+ }
297
+ } catch (error) {
298
+ return {
299
+ status: 400,
300
+ content: JSON.stringify({ error: error.message }),
301
+ headers: { 'Content-Type': 'application/json' }
302
+ }
303
+ }
304
+ }
305
+ }
306
+ ```
307
+
308
+ **Generated endpoints for above example:**
309
+ - Standard CRUD: `GET /wms_layers`, `POST /wms_layers`, etc.
310
+ - Custom business logic: `POST /wms_layers/add-layers`, `PUT /wms_layers/activate/:id`, `GET /wms_layers/search`
311
+
312
+ **SQL Types supported:**
313
+ - `text` / `text not null` - Variable length text
314
+ - `varchar(255)` / `varchar(100) not null` - Fixed length text
315
+ - `integer` / `integer not null` - Whole numbers
316
+ - `boolean` / `boolean default true` - True/false values
317
+ - `datetime` / `timestamp` - Date and time values
318
+ - `real` / `decimal` / `float` - Decimal numbers
319
+
320
+ **Built-in query methods:**
321
+ - `findAll()` - Get all records
322
+ - `findById(id)` - Get specific record
323
+ - `findByColumn(column, value)` - Search by single column
324
+ - `findByColumns(conditions, validation)` - Advanced search with validation
325
+ - `create(data)` - Create new record
326
+ - `update(id, data)` - Update existing record
327
+ - `delete(id)` - Delete record
328
+
141
329
  ## Project Scaffolding
142
330
 
143
331
  Use [create-digitaltwin](https://github.com/CePseudoBE/create-digitaltwin) to quickly bootstrap new projects:
@@ -0,0 +1,122 @@
1
+ import type { AuthenticatedUser } from './types.js';
2
+ /**
3
+ * Parses authentication information from Apache APISIX headers set after Keycloak authentication.
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
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
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // In an AssetsManager handler
18
+ * if (!ApisixAuthParser.hasValidAuth(req.headers)) {
19
+ * return { status: 401, content: 'Authentication required' }
20
+ * }
21
+ *
22
+ * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
23
+ * const userRecord = await this.userService.findOrCreateUser(authUser!)
24
+ * ```
25
+ */
26
+ export declare class ApisixAuthParser {
27
+ /**
28
+ * Extracts user information from APISIX headers.
29
+ *
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)
33
+ *
34
+ * When authentication is disabled (DIGITALTWIN_DISABLE_AUTH=true),
35
+ * returns a default anonymous user instead of requiring headers.
36
+ *
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
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const headers = {
43
+ * 'x-user-id': '6e06a527-a89d-4390-95cd-10ae63cfc939',
44
+ * 'x-user-roles': 'default-roles-master,offline_access'
45
+ * }
46
+ *
47
+ * const authUser = ApisixAuthParser.parseAuthHeaders(headers)
48
+ * // 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
+ * ```
54
+ */
55
+ static parseAuthHeaders(headers: Record<string, string>): AuthenticatedUser | null;
56
+ /**
57
+ * Checks if a request has valid authentication headers.
58
+ *
59
+ * 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.
62
+ *
63
+ * When authentication is disabled (DIGITALTWIN_DISABLE_AUTH=true),
64
+ * this always returns true to allow all requests through.
65
+ *
66
+ * @param headers - HTTP request headers
67
+ * @returns true if x-user-id header is present or auth is disabled, false otherwise
68
+ *
69
+ * @example
70
+ * ```typescript
71
+ * // Early authentication check in handler
72
+ * if (!ApisixAuthParser.hasValidAuth(req.headers)) {
73
+ * return { status: 401, content: 'Authentication required' }
74
+ * }
75
+ *
76
+ * // Now safe to proceed with parsing
77
+ * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
78
+ * ```
79
+ */
80
+ static hasValidAuth(headers: Record<string, string>): boolean;
81
+ /**
82
+ * Extracts just the user ID from headers.
83
+ *
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.
88
+ *
89
+ * @param headers - HTTP request headers
90
+ * @returns Keycloak user ID, anonymous user ID if auth disabled, or null if not present
91
+ *
92
+ * @example
93
+ * ```typescript
94
+ * const userId = ApisixAuthParser.getUserId(req.headers)
95
+ * if (userId) {
96
+ * console.log(`Request from user: ${userId}`)
97
+ * }
98
+ * ```
99
+ */
100
+ static getUserId(headers: Record<string, string>): string | null;
101
+ /**
102
+ * Extracts just the user roles from headers.
103
+ *
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
+ * @param headers - HTTP request headers
110
+ * @returns Array of role names, anonymous roles if auth disabled, empty array if no roles header present
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const roles = ApisixAuthParser.getUserRoles(req.headers)
115
+ * if (roles.includes('admin')) {
116
+ * console.log('User has admin privileges')
117
+ * }
118
+ * ```
119
+ */
120
+ static getUserRoles(headers: Record<string, string>): string[];
121
+ }
122
+ //# sourceMappingURL=apisix_parser.d.ts.map
@@ -0,0 +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;CASjE"}
@@ -0,0 +1,157 @@
1
+ import { AuthConfig } from './auth_config.js';
2
+ /**
3
+ * Parses authentication information from Apache APISIX headers set after Keycloak authentication.
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
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
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * // In an AssetsManager handler
18
+ * if (!ApisixAuthParser.hasValidAuth(req.headers)) {
19
+ * return { status: 401, content: 'Authentication required' }
20
+ * }
21
+ *
22
+ * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
23
+ * const userRecord = await this.userService.findOrCreateUser(authUser!)
24
+ * ```
25
+ */
26
+ export class ApisixAuthParser {
27
+ /**
28
+ * Extracts user information from APISIX headers.
29
+ *
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)
33
+ *
34
+ * When authentication is disabled (DIGITALTWIN_DISABLE_AUTH=true),
35
+ * returns a default anonymous user instead of requiring headers.
36
+ *
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
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * const headers = {
43
+ * 'x-user-id': '6e06a527-a89d-4390-95cd-10ae63cfc939',
44
+ * 'x-user-roles': 'default-roles-master,offline_access'
45
+ * }
46
+ *
47
+ * const authUser = ApisixAuthParser.parseAuthHeaders(headers)
48
+ * // 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
+ * ```
54
+ */
55
+ 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
+ };
71
+ }
72
+ /**
73
+ * Checks if a request has valid authentication headers.
74
+ *
75
+ * 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.
78
+ *
79
+ * When authentication is disabled (DIGITALTWIN_DISABLE_AUTH=true),
80
+ * this always returns true to allow all requests through.
81
+ *
82
+ * @param headers - HTTP request headers
83
+ * @returns true if x-user-id header is present or auth is disabled, false otherwise
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * // Early authentication check in handler
88
+ * if (!ApisixAuthParser.hasValidAuth(req.headers)) {
89
+ * return { status: 401, content: 'Authentication required' }
90
+ * }
91
+ *
92
+ * // Now safe to proceed with parsing
93
+ * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
94
+ * ```
95
+ */
96
+ 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'];
102
+ }
103
+ /**
104
+ * Extracts just the user ID from headers.
105
+ *
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.
110
+ *
111
+ * @param headers - HTTP request headers
112
+ * @returns Keycloak user ID, anonymous user ID if auth disabled, or null if not present
113
+ *
114
+ * @example
115
+ * ```typescript
116
+ * const userId = ApisixAuthParser.getUserId(req.headers)
117
+ * if (userId) {
118
+ * console.log(`Request from user: ${userId}`)
119
+ * }
120
+ * ```
121
+ */
122
+ 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;
128
+ }
129
+ /**
130
+ * Extracts just the user roles from headers.
131
+ *
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
+ * @param headers - HTTP request headers
138
+ * @returns Array of role names, anonymous roles if auth disabled, empty array if no roles header present
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const roles = ApisixAuthParser.getUserRoles(req.headers)
143
+ * if (roles.includes('admin')) {
144
+ * console.log('User has admin privileges')
145
+ * }
146
+ * ```
147
+ */
148
+ 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()) : [];
155
+ }
156
+ }
157
+ //# sourceMappingURL=apisix_parser.js.map
@@ -0,0 +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;CACJ"}
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Authentication configuration for Digital Twin framework.
3
+ *
4
+ * Controls whether authentication is required for components that support it.
5
+ * When authentication is disabled, all requests are treated as authenticated
6
+ * with a default anonymous user.
7
+ *
8
+ * Environment variables:
9
+ * - DIGITALTWIN_DISABLE_AUTH: Set to "true" or "1" to disable authentication (default: false)
10
+ * - DIGITALTWIN_ANONYMOUS_USER_ID: User ID to use when auth is disabled (default: "anonymous")
11
+ *
12
+ * @example
13
+ * ```bash
14
+ * # Disable authentication for development
15
+ * export DIGITALTWIN_DISABLE_AUTH=true
16
+ * export DIGITALTWIN_ANONYMOUS_USER_ID=dev-user-123
17
+ *
18
+ * # Enable authentication (default)
19
+ * export DIGITALTWIN_DISABLE_AUTH=false
20
+ * ```
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * import { AuthConfig } from './auth_config.js'
25
+ *
26
+ * if (AuthConfig.isAuthDisabled()) {
27
+ * console.log('Authentication is disabled')
28
+ * const anonymousUser = AuthConfig.getAnonymousUser()
29
+ * console.log(`Using anonymous user: ${anonymousUser.id}`)
30
+ * }
31
+ * ```
32
+ */
33
+ export declare class AuthConfig {
34
+ private static _config;
35
+ /**
36
+ * Loads and validates authentication configuration from environment variables.
37
+ * This is called automatically the first time any method is used.
38
+ */
39
+ private static loadConfig;
40
+ /**
41
+ * Checks if authentication is disabled via environment variables.
42
+ *
43
+ * @returns true if DIGITALTWIN_DISABLE_AUTH is set to "true" or "1", false otherwise
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * if (AuthConfig.isAuthDisabled()) {
48
+ * console.log('Running in no-auth mode')
49
+ * }
50
+ * ```
51
+ */
52
+ static isAuthDisabled(): boolean;
53
+ /**
54
+ * Checks if authentication is enabled (opposite of isAuthDisabled).
55
+ *
56
+ * @returns true if authentication should be enforced, false otherwise
57
+ */
58
+ static isAuthEnabled(): boolean;
59
+ /**
60
+ * Gets the anonymous user ID to use when authentication is disabled.
61
+ *
62
+ * @returns The user ID configured for anonymous access
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const userId = AuthConfig.getAnonymousUserId()
67
+ * console.log(`Anonymous user ID: ${userId}`) // "anonymous" by default
68
+ * ```
69
+ */
70
+ static getAnonymousUserId(): string;
71
+ /**
72
+ * Gets a fake authenticated user object for anonymous access.
73
+ *
74
+ * @returns An AuthenticatedUser object representing the anonymous user
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * import type { AuthenticatedUser } from './types.js'
79
+ *
80
+ * const anonymousUser: AuthenticatedUser = AuthConfig.getAnonymousUser()
81
+ * console.log(anonymousUser) // { id: "anonymous", roles: ["anonymous"] }
82
+ * ```
83
+ */
84
+ static getAnonymousUser(): {
85
+ id: string;
86
+ roles: string[];
87
+ };
88
+ /**
89
+ * Resets the cached configuration (useful for testing).
90
+ *
91
+ * @private
92
+ */
93
+ static _resetConfig(): void;
94
+ }
95
+ //# sourceMappingURL=auth_config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth_config.d.ts","sourceRoot":"","sources":["../../src/auth/auth_config.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,UAAU;IACnB,OAAO,CAAC,MAAM,CAAC,OAAO,CAGP;IAEf;;;OAGG;IACH,OAAO,CAAC,MAAM,CAAC,UAAU;IA2BzB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,cAAc,IAAI,OAAO;IAKhC;;;;OAIG;IACH,MAAM,CAAC,aAAa,IAAI,OAAO;IAI/B;;;;;;;;;;OAUG;IACH,MAAM,CAAC,kBAAkB,IAAI,MAAM;IAKnC;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,gBAAgB;;;;IAOvB;;;;OAIG;IACH,MAAM,CAAC,YAAY;CAGtB"}