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.
- package/README.md +188 -0
- package/dist/auth/apisix_parser.d.ts +122 -0
- package/dist/auth/apisix_parser.d.ts.map +1 -0
- package/dist/auth/apisix_parser.js +157 -0
- package/dist/auth/apisix_parser.js.map +1 -0
- package/dist/auth/auth_config.d.ts +95 -0
- package/dist/auth/auth_config.d.ts.map +1 -0
- package/dist/auth/auth_config.js +125 -0
- package/dist/auth/auth_config.js.map +1 -0
- package/dist/auth/index.d.ts +5 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +4 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/types.d.ts +100 -0
- package/dist/auth/types.d.ts.map +1 -0
- package/dist/auth/types.js +2 -0
- package/dist/auth/types.js.map +1 -0
- package/dist/auth/user_service.d.ts +86 -0
- package/dist/auth/user_service.d.ts.map +1 -0
- package/dist/auth/user_service.js +239 -0
- package/dist/auth/user_service.js.map +1 -0
- package/dist/components/assets_manager.d.ts +21 -5
- package/dist/components/assets_manager.d.ts.map +1 -1
- package/dist/components/assets_manager.js +132 -4
- package/dist/components/assets_manager.js.map +1 -1
- package/dist/components/custom_table_manager.d.ts +344 -0
- package/dist/components/custom_table_manager.d.ts.map +1 -0
- package/dist/components/custom_table_manager.js +525 -0
- package/dist/components/custom_table_manager.js.map +1 -0
- package/dist/components/index.d.ts +4 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +3 -0
- package/dist/components/index.js.map +1 -1
- package/dist/components/map_manager.d.ts +61 -0
- package/dist/components/map_manager.d.ts.map +1 -0
- package/dist/components/map_manager.js +233 -0
- package/dist/components/map_manager.js.map +1 -0
- package/dist/components/tileset_manager.d.ts +55 -0
- package/dist/components/tileset_manager.d.ts.map +1 -0
- package/dist/components/tileset_manager.js +212 -0
- package/dist/components/tileset_manager.js.map +1 -0
- package/dist/components/types.d.ts +20 -0
- package/dist/components/types.d.ts.map +1 -1
- package/dist/database/adapters/knex_database_adapter.d.ts +7 -0
- package/dist/database/adapters/knex_database_adapter.d.ts.map +1 -1
- package/dist/database/adapters/knex_database_adapter.js +136 -2
- package/dist/database/adapters/knex_database_adapter.js.map +1 -1
- package/dist/database/database_adapter.d.ts +22 -1
- 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 +4 -1
- package/dist/engine/digital_twin_engine.d.ts.map +1 -1
- package/dist/engine/digital_twin_engine.js +69 -3
- package/dist/engine/digital_twin_engine.js.map +1 -1
- package/dist/engine/endpoints.d.ts +2 -2
- package/dist/engine/endpoints.d.ts.map +1 -1
- package/dist/engine/endpoints.js.map +1 -1
- package/dist/env/env.d.ts +8 -0
- package/dist/env/env.d.ts.map +1 -1
- package/dist/env/env.js +22 -1
- package/dist/env/env.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/types/data_record.d.ts +1 -1
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/zip_utils.d.ts +24 -0
- package/dist/utils/zip_utils.d.ts.map +1 -0
- package/dist/utils/zip_utils.js +77 -0
- package/dist/utils/zip_utils.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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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"}
|