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
@@ -0,0 +1,125 @@
1
+ import { Env } from '../env/env.js';
2
+ /**
3
+ * Authentication configuration for Digital Twin framework.
4
+ *
5
+ * Controls whether authentication is required for components that support it.
6
+ * When authentication is disabled, all requests are treated as authenticated
7
+ * with a default anonymous user.
8
+ *
9
+ * Environment variables:
10
+ * - DIGITALTWIN_DISABLE_AUTH: Set to "true" or "1" to disable authentication (default: false)
11
+ * - DIGITALTWIN_ANONYMOUS_USER_ID: User ID to use when auth is disabled (default: "anonymous")
12
+ *
13
+ * @example
14
+ * ```bash
15
+ * # Disable authentication for development
16
+ * export DIGITALTWIN_DISABLE_AUTH=true
17
+ * export DIGITALTWIN_ANONYMOUS_USER_ID=dev-user-123
18
+ *
19
+ * # Enable authentication (default)
20
+ * export DIGITALTWIN_DISABLE_AUTH=false
21
+ * ```
22
+ *
23
+ * @example
24
+ * ```typescript
25
+ * import { AuthConfig } from './auth_config.js'
26
+ *
27
+ * if (AuthConfig.isAuthDisabled()) {
28
+ * console.log('Authentication is disabled')
29
+ * const anonymousUser = AuthConfig.getAnonymousUser()
30
+ * console.log(`Using anonymous user: ${anonymousUser.id}`)
31
+ * }
32
+ * ```
33
+ */
34
+ export class AuthConfig {
35
+ static { this._config = null; }
36
+ /**
37
+ * Loads and validates authentication configuration from environment variables.
38
+ * This is called automatically the first time any method is used.
39
+ */
40
+ static loadConfig() {
41
+ if (this._config !== null)
42
+ return;
43
+ const config = Env.validate({
44
+ DIGITALTWIN_DISABLE_AUTH: Env.schema.boolean({
45
+ optional: true,
46
+ default: false
47
+ }),
48
+ DIGITALTWIN_ANONYMOUS_USER_ID: Env.schema.string({
49
+ optional: true
50
+ })
51
+ });
52
+ // Set default anonymous user ID if not provided
53
+ if (!config.DIGITALTWIN_ANONYMOUS_USER_ID) {
54
+ config.DIGITALTWIN_ANONYMOUS_USER_ID = 'anonymous';
55
+ }
56
+ this._config = config;
57
+ }
58
+ /**
59
+ * Checks if authentication is disabled via environment variables.
60
+ *
61
+ * @returns true if DIGITALTWIN_DISABLE_AUTH is set to "true" or "1", false otherwise
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * if (AuthConfig.isAuthDisabled()) {
66
+ * console.log('Running in no-auth mode')
67
+ * }
68
+ * ```
69
+ */
70
+ static isAuthDisabled() {
71
+ this.loadConfig();
72
+ return this._config.DIGITALTWIN_DISABLE_AUTH;
73
+ }
74
+ /**
75
+ * Checks if authentication is enabled (opposite of isAuthDisabled).
76
+ *
77
+ * @returns true if authentication should be enforced, false otherwise
78
+ */
79
+ static isAuthEnabled() {
80
+ return !this.isAuthDisabled();
81
+ }
82
+ /**
83
+ * Gets the anonymous user ID to use when authentication is disabled.
84
+ *
85
+ * @returns The user ID configured for anonymous access
86
+ *
87
+ * @example
88
+ * ```typescript
89
+ * const userId = AuthConfig.getAnonymousUserId()
90
+ * console.log(`Anonymous user ID: ${userId}`) // "anonymous" by default
91
+ * ```
92
+ */
93
+ static getAnonymousUserId() {
94
+ this.loadConfig();
95
+ return this._config.DIGITALTWIN_ANONYMOUS_USER_ID;
96
+ }
97
+ /**
98
+ * Gets a fake authenticated user object for anonymous access.
99
+ *
100
+ * @returns An AuthenticatedUser object representing the anonymous user
101
+ *
102
+ * @example
103
+ * ```typescript
104
+ * import type { AuthenticatedUser } from './types.js'
105
+ *
106
+ * const anonymousUser: AuthenticatedUser = AuthConfig.getAnonymousUser()
107
+ * console.log(anonymousUser) // { id: "anonymous", roles: ["anonymous"] }
108
+ * ```
109
+ */
110
+ static getAnonymousUser() {
111
+ return {
112
+ id: this.getAnonymousUserId(),
113
+ roles: ['anonymous']
114
+ };
115
+ }
116
+ /**
117
+ * Resets the cached configuration (useful for testing).
118
+ *
119
+ * @private
120
+ */
121
+ static _resetConfig() {
122
+ this._config = null;
123
+ }
124
+ }
125
+ //# sourceMappingURL=auth_config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth_config.js","sourceRoot":"","sources":["../../src/auth/auth_config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,eAAe,CAAA;AAEnC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,OAAO,UAAU;aACJ,YAAO,GAGX,IAAI,CAAA;IAEf;;;OAGG;IACK,MAAM,CAAC,UAAU;QACrB,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;YAAE,OAAM;QAEjC,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAC;YACxB,wBAAwB,EAAE,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC;gBACzC,QAAQ,EAAE,IAAI;gBACd,OAAO,EAAE,KAAK;aACjB,CAAC;YACF,6BAA6B,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC;gBAC7C,QAAQ,EAAE,IAAI;aACjB,CAAC;SACL,CAGA,CAAA;QAED,gDAAgD;QAChD,IAAI,CAAC,MAAM,CAAC,6BAA6B,EAAE,CAAC;YACxC,MAAM,CAAC,6BAA6B,GAAG,WAAW,CAAA;QACtD,CAAC;QAED,IAAI,CAAC,OAAO,GAAG,MAGd,CAAA;IACL,CAAC;IAED;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,cAAc;QACjB,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,OAAO,IAAI,CAAC,OAAQ,CAAC,wBAAwB,CAAA;IACjD,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,aAAa;QAChB,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,CAAA;IACjC,CAAC;IAED;;;;;;;;;;OAUG;IACH,MAAM,CAAC,kBAAkB;QACrB,IAAI,CAAC,UAAU,EAAE,CAAA;QACjB,OAAO,IAAI,CAAC,OAAQ,CAAC,6BAA6B,CAAA;IACtD,CAAC;IAED;;;;;;;;;;;;OAYG;IACH,MAAM,CAAC,gBAAgB;QACnB,OAAO;YACH,EAAE,EAAE,IAAI,CAAC,kBAAkB,EAAE;YAC7B,KAAK,EAAE,CAAC,WAAW,CAAC;SACvB,CAAA;IACL,CAAC;IAED;;;;OAIG;IACH,MAAM,CAAC,YAAY;QACf,IAAI,CAAC,OAAO,GAAG,IAAI,CAAA;IACvB,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { ApisixAuthParser } from './apisix_parser.js';
2
+ export { UserService } from './user_service.js';
3
+ export { AuthConfig } from './auth_config.js';
4
+ export type { AuthenticatedUser, UserRecord, AuthContext, AuthenticatedRequest } from './types.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,YAAY,EAAE,iBAAiB,EAAE,UAAU,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,4 @@
1
+ export { ApisixAuthParser } from './apisix_parser.js';
2
+ export { UserService } from './user_service.js';
3
+ export { AuthConfig } from './auth_config.js';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,100 @@
1
+ /**
2
+ * User information extracted from Keycloak JWT via Apache APISIX headers.
3
+ *
4
+ * This interface represents the authenticated user data parsed from APISIX
5
+ * headers after Keycloak authentication. APISIX forwards these headers:
6
+ * - `x-user-id`: The Keycloak user UUID
7
+ * - `x-user-roles`: Comma-separated list of user roles
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
12
+ * if (authUser) {
13
+ * console.log(`User ${authUser.id} has roles: ${authUser.roles.join(', ')}`)
14
+ * }
15
+ * ```
16
+ */
17
+ export interface AuthenticatedUser {
18
+ /** User ID from Keycloak (x-user-id header) - UUID format */
19
+ id: string;
20
+ /** User roles from Keycloak (x-user-roles header, parsed from comma-separated string) */
21
+ roles: string[];
22
+ }
23
+ /**
24
+ * User record stored in the database.
25
+ *
26
+ * Represents a user stored in the normalized user management system.
27
+ * Users are created automatically when they first access the system
28
+ * after being authenticated by Keycloak via APISIX.
29
+ *
30
+ * @example
31
+ * ```typescript
32
+ * const userService = new UserService(database)
33
+ * const userRecord = await userService.findOrCreateUser(authenticatedUser)
34
+ * console.log(`User ${userRecord.keycloak_id} has ${userRecord.roles.length} roles`)
35
+ * ```
36
+ */
37
+ export interface UserRecord {
38
+ /** Primary key (auto-increment) */
39
+ id?: number;
40
+ /** Keycloak user ID (UUID, unique across system) */
41
+ keycloak_id: string;
42
+ /** User roles (populated from user_roles junction table) */
43
+ roles: string[];
44
+ /** First time the user was seen in the system */
45
+ created_at: Date;
46
+ /** Last time the user's roles were updated */
47
+ updated_at: Date;
48
+ }
49
+ /**
50
+ * Authentication context passed to handlers.
51
+ *
52
+ * Contains both the raw authentication data from APISIX headers
53
+ * and the corresponding database user record. Used internally
54
+ * by components that need full user context.
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const authContext: AuthContext = {
59
+ * user: authUser,
60
+ * userRecord: await userService.findOrCreateUser(authUser)
61
+ * }
62
+ * ```
63
+ */
64
+ export interface AuthContext {
65
+ /** Authenticated user information from APISIX headers */
66
+ user: AuthenticatedUser;
67
+ /** Database user record with full role information */
68
+ userRecord: UserRecord;
69
+ }
70
+ /**
71
+ * Request object extended with authentication context.
72
+ *
73
+ * Represents an HTTP request that has been augmented with
74
+ * authentication information. Used by handlers that need
75
+ * access to both request data and user context.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * async function handleRequest(req: AuthenticatedRequest) {
80
+ * if (req.auth) {
81
+ * console.log(`Request from user: ${req.auth.user.id}`)
82
+ * }
83
+ * }
84
+ * ```
85
+ */
86
+ export interface AuthenticatedRequest {
87
+ /** Original Express request object */
88
+ originalRequest: any;
89
+ /** Authentication context (undefined if not authenticated) */
90
+ auth?: AuthContext;
91
+ /** Request headers (including APISIX authentication headers) */
92
+ headers: Record<string, string>;
93
+ /** URL parameters */
94
+ params?: Record<string, string>;
95
+ /** Request body */
96
+ body?: any;
97
+ /** File upload (for multipart requests with assets) */
98
+ file?: any;
99
+ }
100
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,iBAAiB;IAC9B,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAA;IACV,yFAAyF;IACzF,KAAK,EAAE,MAAM,EAAE,CAAA;CAClB;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,UAAU;IACvB,mCAAmC;IACnC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,4DAA4D;IAC5D,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,iDAAiD;IACjD,UAAU,EAAE,IAAI,CAAA;IAChB,8CAA8C;IAC9C,UAAU,EAAE,IAAI,CAAA;CACnB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,WAAW;IACxB,yDAAyD;IACzD,IAAI,EAAE,iBAAiB,CAAA;IACvB,sDAAsD;IACtD,UAAU,EAAE,UAAU,CAAA;CACzB;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,oBAAoB;IACjC,sCAAsC;IACtC,eAAe,EAAE,GAAG,CAAA;IACpB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,qBAAqB;IACrB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,mBAAmB;IACnB,IAAI,CAAC,EAAE,GAAG,CAAA;IACV,uDAAuD;IACvD,IAAI,CAAC,EAAE,GAAG,CAAA;CACb"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/auth/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,86 @@
1
+ import type { DatabaseAdapter } from '../database/database_adapter.js';
2
+ import type { AuthenticatedUser, UserRecord } from './types.js';
3
+ /**
4
+ * Service for managing users in the Digital Twin framework.
5
+ *
6
+ * This service handles the complete user lifecycle in a Digital Twin application
7
+ * with Keycloak authentication via Apache APISIX. It manages a normalized database
8
+ * schema with three tables:
9
+ *
10
+ * - `users`: Core user records linked to Keycloak IDs
11
+ * - `roles`: Master list of available roles
12
+ * - `user_roles`: Many-to-many relationship between users and roles
13
+ *
14
+ * Key features:
15
+ * - Automatic user creation on first authentication
16
+ * - Role synchronization with Keycloak
17
+ * - Optimized queries with proper indexing
18
+ * - Transaction-safe role updates
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * // Initialize in your Digital Twin engine
23
+ * const userService = new UserService(databaseAdapter)
24
+ * await userService.initializeTables()
25
+ *
26
+ * // Use in AssetsManager handlers
27
+ * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
28
+ * const userRecord = await userService.findOrCreateUser(authUser!)
29
+ *
30
+ * // Link assets to users
31
+ * await this.uploadAsset({
32
+ * description: 'My file',
33
+ * source: 'upload',
34
+ * owner_id: userRecord.id!.toString(),
35
+ * filename: 'document.pdf',
36
+ * file: buffer
37
+ * })
38
+ * ```
39
+ */
40
+ export declare class UserService {
41
+ private db;
42
+ private readonly usersTable;
43
+ private readonly rolesTable;
44
+ private readonly userRolesTable;
45
+ constructor(db: DatabaseAdapter);
46
+ /**
47
+ * Ensures all user-related tables exist in the database
48
+ */
49
+ initializeTables(): Promise<void>;
50
+ /**
51
+ * Finds or creates a user and synchronizes their roles.
52
+ *
53
+ * When authentication is disabled, returns a mock user record
54
+ * without touching the database.
55
+ */
56
+ findOrCreateUser(authUser: AuthenticatedUser): Promise<UserRecord>;
57
+ /**
58
+ * Gets a user by their database ID
59
+ */
60
+ getUserById(id: number): Promise<UserRecord | undefined>;
61
+ /**
62
+ * Gets a user by their Keycloak ID with roles
63
+ */
64
+ getUserByKeycloakId(keycloakId: string): Promise<UserRecord | undefined>;
65
+ /**
66
+ * Gets the underlying Knex instance from the database adapter
67
+ */
68
+ private getKnex;
69
+ /**
70
+ * Finds a user by their Keycloak ID
71
+ */
72
+ private findUserByKeycloakId;
73
+ /**
74
+ * Creates a new user record
75
+ */
76
+ private createUser;
77
+ /**
78
+ * Synchronizes user roles with what's provided by Keycloak
79
+ */
80
+ private syncUserRoles;
81
+ /**
82
+ * Gets a user with their roles populated
83
+ */
84
+ private getUserWithRoles;
85
+ }
86
+ //# sourceMappingURL=user_service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user_service.d.ts","sourceRoot":"","sources":["../../src/auth/user_service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iCAAiC,CAAA;AACtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAI/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,qBAAa,WAAW;IACpB,OAAO,CAAC,EAAE,CAAiB;IAC3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAe;gBAElC,EAAE,EAAE,eAAe;IAI/B;;OAEG;IACG,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC;IAyDvC;;;;;OAKG;IACG,gBAAgB,CAAC,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,CAAC;IA0BxE;;OAEG;IACG,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAI9D;;OAEG;IACG,mBAAmB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC;IAY9E;;OAEG;IACH,OAAO,CAAC,OAAO;IAOf;;OAEG;YACW,oBAAoB;IAgBlC;;OAEG;YACW,UAAU;IAuBxB;;OAEG;YACW,aAAa;IAoC3B;;OAEG;YACW,gBAAgB;CAqCjC"}
@@ -0,0 +1,239 @@
1
+ import { AuthConfig } from './auth_config.js';
2
+ /**
3
+ * Service for managing users in the Digital Twin framework.
4
+ *
5
+ * This service handles the complete user lifecycle in a Digital Twin application
6
+ * with Keycloak authentication via Apache APISIX. It manages a normalized database
7
+ * schema with three tables:
8
+ *
9
+ * - `users`: Core user records linked to Keycloak IDs
10
+ * - `roles`: Master list of available roles
11
+ * - `user_roles`: Many-to-many relationship between users and roles
12
+ *
13
+ * Key features:
14
+ * - Automatic user creation on first authentication
15
+ * - Role synchronization with Keycloak
16
+ * - Optimized queries with proper indexing
17
+ * - Transaction-safe role updates
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // Initialize in your Digital Twin engine
22
+ * const userService = new UserService(databaseAdapter)
23
+ * await userService.initializeTables()
24
+ *
25
+ * // Use in AssetsManager handlers
26
+ * const authUser = ApisixAuthParser.parseAuthHeaders(req.headers)
27
+ * const userRecord = await userService.findOrCreateUser(authUser!)
28
+ *
29
+ * // Link assets to users
30
+ * await this.uploadAsset({
31
+ * description: 'My file',
32
+ * source: 'upload',
33
+ * owner_id: userRecord.id!.toString(),
34
+ * filename: 'document.pdf',
35
+ * file: buffer
36
+ * })
37
+ * ```
38
+ */
39
+ export class UserService {
40
+ constructor(db) {
41
+ this.usersTable = 'users';
42
+ this.rolesTable = 'roles';
43
+ this.userRolesTable = 'user_roles';
44
+ this.db = db;
45
+ }
46
+ /**
47
+ * Ensures all user-related tables exist in the database
48
+ */
49
+ async initializeTables() {
50
+ const knex = this.getKnex();
51
+ console.log('Creating user management tables...');
52
+ // 1. Create roles table
53
+ if (!(await knex.schema.hasTable(this.rolesTable))) {
54
+ await knex.schema.createTable(this.rolesTable, table => {
55
+ table.increments('id').primary();
56
+ table.string('name', 100).notNullable().unique();
57
+ table.timestamp('created_at').defaultTo(knex.fn.now());
58
+ // Index pour les recherches par nom de rôle
59
+ table.index('name', 'roles_idx_name');
60
+ });
61
+ console.log('Roles table created');
62
+ }
63
+ // 2. Create users table
64
+ if (!(await knex.schema.hasTable(this.usersTable))) {
65
+ await knex.schema.createTable(this.usersTable, table => {
66
+ table.increments('id').primary();
67
+ table.string('keycloak_id', 255).notNullable().unique();
68
+ table.timestamp('created_at').defaultTo(knex.fn.now());
69
+ table.timestamp('updated_at').defaultTo(knex.fn.now());
70
+ // Index principal pour les lookups par keycloak_id
71
+ table.index('keycloak_id', 'users_idx_keycloak_id');
72
+ table.index('created_at', 'users_idx_created_at');
73
+ });
74
+ console.log('Users table created');
75
+ }
76
+ // 3. Create user_roles junction table
77
+ if (!(await knex.schema.hasTable(this.userRolesTable))) {
78
+ await knex.schema.createTable(this.userRolesTable, table => {
79
+ table.integer('user_id').unsigned().notNullable();
80
+ table.integer('role_id').unsigned().notNullable();
81
+ table.timestamp('created_at').defaultTo(knex.fn.now());
82
+ // Clé primaire composite
83
+ table.primary(['user_id', 'role_id']);
84
+ // Clés étrangères
85
+ table.foreign('user_id').references('id').inTable(this.usersTable).onDelete('CASCADE');
86
+ table.foreign('role_id').references('id').inTable(this.rolesTable).onDelete('CASCADE');
87
+ // Index pour les requêtes inverses (quels utilisateurs ont ce rôle)
88
+ table.index('role_id', 'user_roles_idx_role_id');
89
+ table.index('user_id', 'user_roles_idx_user_id');
90
+ });
91
+ console.log('User_roles table created');
92
+ }
93
+ console.log('All user management tables ready');
94
+ }
95
+ /**
96
+ * Finds or creates a user and synchronizes their roles.
97
+ *
98
+ * When authentication is disabled, returns a mock user record
99
+ * without touching the database.
100
+ */
101
+ async findOrCreateUser(authUser) {
102
+ // If authentication is disabled, return a mock user record
103
+ if (AuthConfig.isAuthDisabled()) {
104
+ return {
105
+ id: 1, // Use a consistent ID for anonymous user
106
+ keycloak_id: authUser.id,
107
+ roles: authUser.roles,
108
+ created_at: new Date(),
109
+ updated_at: new Date()
110
+ };
111
+ }
112
+ // 1. Find or create user
113
+ let userRecord = await this.findUserByKeycloakId(authUser.id);
114
+ if (!userRecord) {
115
+ userRecord = await this.createUser(authUser);
116
+ }
117
+ // 2. Synchronize roles
118
+ await this.syncUserRoles(userRecord.id, authUser.roles);
119
+ // 3. Return user with current roles
120
+ return (await this.getUserWithRoles(userRecord.id)) || userRecord;
121
+ }
122
+ /**
123
+ * Gets a user by their database ID
124
+ */
125
+ async getUserById(id) {
126
+ return await this.getUserWithRoles(id);
127
+ }
128
+ /**
129
+ * Gets a user by their Keycloak ID with roles
130
+ */
131
+ async getUserByKeycloakId(keycloakId) {
132
+ const knex = this.getKnex();
133
+ const userRow = (await knex(this.usersTable).where('keycloak_id', keycloakId).first());
134
+ if (!userRow)
135
+ return undefined;
136
+ return await this.getUserWithRoles(userRow.id);
137
+ }
138
+ /**
139
+ * Gets the underlying Knex instance from the database adapter
140
+ */
141
+ getKnex() {
142
+ if ('getKnex' in this.db && typeof this.db.getKnex === 'function') {
143
+ return this.db.getKnex();
144
+ }
145
+ throw new Error('Cannot access Knex instance from DatabaseAdapter');
146
+ }
147
+ /**
148
+ * Finds a user by their Keycloak ID
149
+ */
150
+ async findUserByKeycloakId(keycloakId) {
151
+ const knex = this.getKnex();
152
+ const row = await knex(this.usersTable).where('keycloak_id', keycloakId).first();
153
+ if (!row)
154
+ return undefined;
155
+ return {
156
+ id: row.id,
157
+ keycloak_id: row.keycloak_id,
158
+ roles: [], // Will be populated by getUserWithRoles
159
+ created_at: new Date(row.created_at),
160
+ updated_at: new Date(row.updated_at)
161
+ };
162
+ }
163
+ /**
164
+ * Creates a new user record
165
+ */
166
+ async createUser(authUser) {
167
+ const knex = this.getKnex();
168
+ const now = new Date();
169
+ const userData = {
170
+ keycloak_id: authUser.id,
171
+ created_at: now,
172
+ updated_at: now
173
+ };
174
+ const insertResult = await knex(this.usersTable).insert(userData).returning('id');
175
+ const insertedId = insertResult[0];
176
+ const id = typeof insertedId === 'object' ? insertedId.id : insertedId;
177
+ return {
178
+ id,
179
+ keycloak_id: authUser.id,
180
+ roles: [],
181
+ created_at: now,
182
+ updated_at: now
183
+ };
184
+ }
185
+ /**
186
+ * Synchronizes user roles with what's provided by Keycloak
187
+ */
188
+ async syncUserRoles(userId, newRoles) {
189
+ const knex = this.getKnex();
190
+ // Transaction pour assurer la cohérence
191
+ await knex.transaction(async (trx) => {
192
+ // 1. Ensure all roles exist in roles table
193
+ for (const roleName of newRoles) {
194
+ await trx(this.rolesTable).insert({ name: roleName }).onConflict('name').ignore(); // Si le rôle existe déjà, on l'ignore
195
+ }
196
+ // 2. Get role IDs
197
+ const roleRows = (await trx(this.rolesTable).select('id', 'name').whereIn('name', newRoles));
198
+ const roleIds = roleRows.map(r => r.id);
199
+ // 3. Remove old role associations
200
+ await trx(this.userRolesTable).where('user_id', userId).delete();
201
+ // 4. Add new role associations
202
+ if (roleIds.length > 0) {
203
+ const userRoleData = roleIds.map((roleId) => ({
204
+ user_id: userId,
205
+ role_id: roleId
206
+ }));
207
+ await trx(this.userRolesTable).insert(userRoleData);
208
+ }
209
+ // 5. Update user's updated_at timestamp
210
+ await trx(this.usersTable).where('id', userId).update({ updated_at: new Date() });
211
+ });
212
+ }
213
+ /**
214
+ * Gets a user with their roles populated
215
+ */
216
+ async getUserWithRoles(userId) {
217
+ const knex = this.getKnex();
218
+ // Join query to get user + roles
219
+ const result = (await knex(this.usersTable)
220
+ .leftJoin(this.userRolesTable, `${this.usersTable}.id`, `${this.userRolesTable}.user_id`)
221
+ .leftJoin(this.rolesTable, `${this.userRolesTable}.role_id`, `${this.rolesTable}.id`)
222
+ .select(`${this.usersTable}.id`, `${this.usersTable}.keycloak_id`, `${this.usersTable}.created_at`, `${this.usersTable}.updated_at`, `${this.rolesTable}.name as role_name`)
223
+ .where(`${this.usersTable}.id`, userId));
224
+ if (result.length === 0)
225
+ return undefined;
226
+ const userRow = result[0];
227
+ const roles = result
228
+ .filter(row => row.role_name) // Filter out null roles
229
+ .map(row => row.role_name);
230
+ return {
231
+ id: userRow.id,
232
+ keycloak_id: userRow.keycloak_id,
233
+ roles: roles,
234
+ created_at: new Date(userRow.created_at),
235
+ updated_at: new Date(userRow.updated_at)
236
+ };
237
+ }
238
+ }
239
+ //# sourceMappingURL=user_service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user_service.js","sourceRoot":"","sources":["../../src/auth/user_service.ts"],"names":[],"mappings":"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"}