@ti-engine/core 1.4.1 → 1.6.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/CHANGELOG.md +41 -0
- package/README.md +3 -3
- package/bin/settings.json +1 -1
- package/components/exchange/message-handler.js +14 -4
- package/components/exchange/message-receiver.js +2 -1
- package/package.json +7 -6
- package/utils/cache.js +12 -11
- package/utils/config.js +103 -103
- package/utils/exceptions.js +2 -1
- package/utils/tools.js +20 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
This document contains the list of changes made to the framework. The format is based on the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) specification.
|
|
4
4
|
|
|
5
|
+
## Version 1.6.0
|
|
6
|
+
|
|
7
|
+
* refactor(exchange)!: replace the blake2 keyed hash with native `node:crypto` HMAC-SHA256 in the message integrity check
|
|
8
|
+
* build(deps)!: remove the `blake2` native addon dependency (no longer builds on Node 26; replaced by the built-in `node:crypto`)
|
|
9
|
+
* fix(exchange): use a constant-time comparison for the message security hash
|
|
10
|
+
* fix(exchange): ship an empty default `securityHashKey` and warn (once) when the security hash is enabled with a missing/default key
|
|
11
|
+
* test(exchange): add the first core test suite — message-hash determinism, tamper detection, and the constant-time comparison
|
|
12
|
+
|
|
13
|
+
> **BREAKING (wire):** the message security hash changes from keyed-BLAKE2b (128 hex) to HMAC-SHA256 (64 hex). Old and new core versions produce mutually incompatible hashes. When upgrading across this version with `securityHashEnabled=true`, redeploy all services together — recommended order: set `securityHashEnabled=false` everywhere → deploy the new core to all services → re-enable. HMAC also accepts keys of any length (custom keys >64 bytes that previously failed under blake2b now work). The default `securityHashKey` is now empty: set `TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY` in any real deployment, otherwise tamper protection is ineffective.
|
|
14
|
+
|
|
15
|
+
## Version 1.5.1
|
|
16
|
+
|
|
17
|
+
* build(deps): update `@dotenvx/dotenvx` from ^1.66.0 to ^1.73.1
|
|
18
|
+
* build(deps): update `ioredis` from ^5.10.1 to ^5.11.1
|
|
19
|
+
|
|
20
|
+
## Version 1.5.0
|
|
21
|
+
|
|
22
|
+
* feat(exceptions): add `E_APP_RESOURCE_ALREADY_EXISTS` (5006) for resource-creation conflicts; intended to be raised with HTTP `409`
|
|
23
|
+
|
|
24
|
+
## Version 1.4.6
|
|
25
|
+
|
|
26
|
+
* build(deps): update `@dotenvx/dotenvx` from ^1.65.0 to ^1.66.0
|
|
27
|
+
|
|
28
|
+
## Version 1.4.5
|
|
29
|
+
|
|
30
|
+
* feat(cache): add array-based path support to all `*JSON` methods — passing an array of strings as `path` treats each element as a literal key name using bracket notation, preventing misinterpretation of key names containing dots or other JSONPath special characters
|
|
31
|
+
|
|
32
|
+
## Version 1.4.4
|
|
33
|
+
|
|
34
|
+
* build(deps): update `@dotenvx/dotenvx` from ^1.60.0 to ^1.65.0
|
|
35
|
+
|
|
36
|
+
## Version 1.4.3
|
|
37
|
+
|
|
38
|
+
* build(deps): update `@dotenvx/dotenvx` from ^1.59.1 to ^1.60.0
|
|
39
|
+
|
|
40
|
+
## Version 1.4.2
|
|
41
|
+
|
|
42
|
+
* build(deps): update `@dotenvx/dotenvx` from ^1.55.1 to ^1.59.1
|
|
43
|
+
* build(deps): update `ioredis` from ^5.10.0 to ^5.10.1
|
|
44
|
+
* build(deps): update `lodash` from ^4.17.23 to ^4.18.1
|
|
45
|
+
|
|
5
46
|
## Version 1.4.1
|
|
6
47
|
|
|
7
48
|
* build(deps): update `@dotenvx/dotenvx` from ^1.54.1 to ^1.55.1
|
package/README.md
CHANGED
|
@@ -479,12 +479,12 @@ MESSAGE_EXCHANGE_MESSAGE_STORE (Advanced)
|
|
|
479
479
|
MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED (Advanced)
|
|
480
480
|
: JSON path `messageExchange.securityHashEnabled`, type `boolean`, default `true`
|
|
481
481
|
: ENV variable `TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED`
|
|
482
|
-
: This setting controls whether the message exchange will use a control hash mechanism to ensure there is no tampering with the messages in between service calls. In most cases you would want to keep this enabled since it ensures the integrity of your data. If you are concerned about performance (
|
|
482
|
+
: This setting controls whether the message exchange will use a control hash mechanism to ensure there is no tampering with the messages in between service calls. In most cases you would want to keep this enabled since it ensures the integrity of your data. If you are concerned about performance (the keyed `HMAC-SHA256` hash is rapid, but it still eats some milliseconds) you might want to try and disable this to see if it makes any notable difference.
|
|
483
483
|
|
|
484
484
|
MESSAGE_EXCHANGE_SECURITY_HASH_KEY (Advanced)
|
|
485
|
-
: JSON path `messageExchange.securityHashKey`, type `string`, default `
|
|
485
|
+
: JSON path `messageExchange.securityHashKey`, type `string`, default `empty (must be set)`
|
|
486
486
|
: ENV variable `TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY`
|
|
487
|
-
: This setting holds the
|
|
487
|
+
: This setting holds the HMAC key used by the message exchange control hash mechanism. By default it is empty, which leaves tamper protection ineffective and logs a startup warning. You absolutely must provide your own private key via the ENV variable in any real deployment. Depending on your configuration and infrastructure, it might come from a secure storage, HSM, key vault, etc.
|
|
488
488
|
|
|
489
489
|
MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME
|
|
490
490
|
: JSON path `messageExchange.traceExpirationTime`, type `number`, default `3600`
|
package/bin/settings.json
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"messageQueuePrefix": "ti:messages:",
|
|
27
27
|
"messageStore": "ti:messages:store",
|
|
28
28
|
"securityHashEnabled": true,
|
|
29
|
-
"securityHashKey": "
|
|
29
|
+
"securityHashKey": "",
|
|
30
30
|
"traceExpirationTime": 3600,
|
|
31
31
|
"traceLogEnabled": false,
|
|
32
32
|
"traceRepository": "ti:messages:trace"
|
|
@@ -8,12 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
const ConnectionObserver = require( "#connection-observer" );
|
|
10
10
|
const _ = require( "lodash" );
|
|
11
|
-
const
|
|
11
|
+
const crypto = require( "node:crypto" );
|
|
12
12
|
const exceptions = require( "#exceptions" );
|
|
13
13
|
const logger = require( "#logger" );
|
|
14
14
|
const tools = require( "#tools" );
|
|
15
15
|
const config = require( "#config" );
|
|
16
16
|
|
|
17
|
+
const OLD_DEFAULT_HASH_KEY = "23e7bdc7-a793-41f9-856e-6760332f0c73";
|
|
18
|
+
let keyWarningEmitted = false;
|
|
19
|
+
|
|
17
20
|
/**
|
|
18
21
|
* An abstract class that defines a basic message handler behavior.
|
|
19
22
|
* <br/>
|
|
@@ -120,10 +123,17 @@ class MessageHandler extends ConnectionObserver {
|
|
|
120
123
|
* @public
|
|
121
124
|
*/
|
|
122
125
|
createMessageHash( message ) {
|
|
126
|
+
let key = config.getSetting( config.setting.MESSAGE_EXCHANGE_SECURITY_HASH_KEY );
|
|
127
|
+
if ( keyWarningEmitted === false ) {
|
|
128
|
+
keyWarningEmitted = true;
|
|
129
|
+
if ( !key || key === OLD_DEFAULT_HASH_KEY ) {
|
|
130
|
+
logger.log( "Message-exchange security hash is enabled but no private key is set ('securityHashKey' is missing or the published default). Set TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY to a private value, otherwise tamper protection is ineffective.", logger.logSeverity.WARNING );
|
|
131
|
+
}
|
|
132
|
+
}
|
|
123
133
|
let transformed = tools.decomposeJSON( tools.decycle( message ) );
|
|
124
|
-
let
|
|
125
|
-
|
|
126
|
-
return
|
|
134
|
+
let hmac = crypto.createHmac( "sha256", Buffer.from( key ) );
|
|
135
|
+
hmac.update( Buffer.from( transformed ) );
|
|
136
|
+
return hmac.digest( "hex" );
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
/**
|
|
@@ -10,6 +10,7 @@ const MessageHandler = require( "#message-handler" );
|
|
|
10
10
|
const logger = require( "#logger" );
|
|
11
11
|
const exceptions = require( "#exceptions" );
|
|
12
12
|
const config = require( "#config" );
|
|
13
|
+
const tools = require( "#tools" );
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* An abstract class that defines a basic message receiver behavior.
|
|
@@ -161,7 +162,7 @@ class MessageReceiver extends MessageHandler {
|
|
|
161
162
|
let receivedHash = message.hash;
|
|
162
163
|
delete message.hash;
|
|
163
164
|
let currentHash = this.createMessageHash( message );
|
|
164
|
-
if (
|
|
165
|
+
if ( tools.constantTimeEquals( receivedHash, currentHash ) ) {
|
|
165
166
|
resolve( message );
|
|
166
167
|
} else {
|
|
167
168
|
reject( exceptions.raise( exceptions.exceptionCode.E_SEC_MESSAGE_TAMPERING_DETECTED, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ti-engine/core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"microservices",
|
|
@@ -26,6 +26,9 @@
|
|
|
26
26
|
"./service-provider": "./components/service-provider.js",
|
|
27
27
|
"./tools": "./utils/tools.js"
|
|
28
28
|
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"test": "node --test"
|
|
31
|
+
},
|
|
29
32
|
"imports": {
|
|
30
33
|
"#auditing": "./components/auditing.js",
|
|
31
34
|
"#cache": "./utils/cache.js",
|
|
@@ -56,14 +59,12 @@
|
|
|
56
59
|
"#tools": "./utils/tools.js"
|
|
57
60
|
},
|
|
58
61
|
"dependencies": {
|
|
59
|
-
"@dotenvx/dotenvx": "^1.
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"lodash": "^4.17.23",
|
|
62
|
+
"@dotenvx/dotenvx": "^1.73.1",
|
|
63
|
+
"ioredis": "^5.11.1",
|
|
64
|
+
"lodash": "^4.18.1",
|
|
63
65
|
"node-schedule": "^2.1.1"
|
|
64
66
|
},
|
|
65
67
|
"optionalDependencies": {
|
|
66
|
-
"zeromq": "^6.5.0"
|
|
67
68
|
},
|
|
68
69
|
"files": [
|
|
69
70
|
"bin/",
|
package/utils/cache.js
CHANGED
|
@@ -341,12 +341,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
341
341
|
expireValue( key, seconds, name ) {
|
|
342
342
|
return new Promise( ( resolve, reject ) => {
|
|
343
343
|
if ( this.#isOperational === true ) {
|
|
344
|
-
let commandExpire = [];
|
|
345
|
-
if ( name ) {
|
|
346
|
-
commandExpire = [ redis.cacheCommands.HASH_EXPIRE, name, seconds, "FIELDS", 1, key ];
|
|
347
|
-
} else {
|
|
348
|
-
commandExpire = [ redis.cacheCommands.EXPIRE, key, seconds ];
|
|
349
|
-
}
|
|
344
|
+
let commandExpire = ( name ) ? [ redis.cacheCommands.HASH_EXPIRE, name, seconds, "FIELDS", 1, key ] : [ redis.cacheCommands.EXPIRE, key, seconds ];
|
|
350
345
|
this.#redisClient.executeCommands( [ commandExpire ] ).then( () => {
|
|
351
346
|
resolve( seconds );
|
|
352
347
|
} ).catch( ( error ) => {
|
|
@@ -632,7 +627,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
632
627
|
* @method
|
|
633
628
|
* @param {string} key
|
|
634
629
|
* @param {Object} value
|
|
635
|
-
* @param {string} [path="$"]
|
|
630
|
+
* @param {string|string[]} [path="$"] A dot-separated JSONPath string, or an array of literal key segments (use the array form when key names may contain dots or other special characters).
|
|
636
631
|
* @param {number} [overrideMode=0] By default this allows full override for existing keys.
|
|
637
632
|
* Option 1 will set the key only if it doesn't already exist. Option 2 will set it only if it already exists.
|
|
638
633
|
* @returns {Promise}
|
|
@@ -667,7 +662,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
667
662
|
*
|
|
668
663
|
* @method
|
|
669
664
|
* @param {string} key
|
|
670
|
-
* @param {string} [path="$"]
|
|
665
|
+
* @param {string|string[]} [path="$"] A dot-separated JSONPath string, or an array of literal key segments (use the array form when key names may contain dots or other special characters).
|
|
671
666
|
* @returns {Promise<Object>}
|
|
672
667
|
* @public
|
|
673
668
|
*/
|
|
@@ -698,7 +693,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
698
693
|
* @method
|
|
699
694
|
* @param {string} key
|
|
700
695
|
* @param {Object} value
|
|
701
|
-
* @param {string} [path="$"]
|
|
696
|
+
* @param {string|string[]} [path="$"] A dot-separated JSONPath string, or an array of literal key segments (use the array form when key names may contain dots or other special characters).
|
|
702
697
|
* @returns {Promise}
|
|
703
698
|
* @public
|
|
704
699
|
*/
|
|
@@ -729,7 +724,7 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
729
724
|
* @method
|
|
730
725
|
* @param {string} key
|
|
731
726
|
* @param {Object} value
|
|
732
|
-
* @param {string} [path="$"]
|
|
727
|
+
* @param {string|string[]} [path="$"] A dot-separated JSONPath string, or an array of literal key segments (use the array form when key names may contain dots or other special characters).
|
|
733
728
|
* @returns {Promise}
|
|
734
729
|
* @public
|
|
735
730
|
*/
|
|
@@ -756,13 +751,19 @@ class CommonMemoryCache extends ConnectionObserver {
|
|
|
756
751
|
|
|
757
752
|
/**
|
|
758
753
|
* Used to normalize a JSON path.
|
|
754
|
+
* <br/>
|
|
755
|
+
* NOTE: If "path" is an array, each element is treated as a literal key name and encoded with bracket notation,
|
|
756
|
+
* which correctly handles key names that contain dots or other JSONPath special characters.
|
|
759
757
|
*
|
|
760
758
|
* @method
|
|
761
|
-
* @param {string} path
|
|
759
|
+
* @param {string|string[]} path
|
|
762
760
|
* @returns {string}
|
|
763
761
|
* @private
|
|
764
762
|
*/
|
|
765
763
|
#normalizeJSONPath( path ) {
|
|
764
|
+
if ( Array.isArray( path ) ) {
|
|
765
|
+
return "$" + path.map( ( segment ) => `["${ String( segment ).replace( /\\/g, "\\\\" ).replace( /"/g, '\\"' ) }"]` ).join( "" );
|
|
766
|
+
}
|
|
766
767
|
return ( path.startsWith( "$" ) === false ) ? ( "$." + path ) : path;
|
|
767
768
|
}
|
|
768
769
|
|
package/utils/config.js
CHANGED
|
@@ -1,104 +1,104 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
|
|
3
|
-
* Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
4
|
-
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
5
|
-
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
6
|
-
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const _ = require( "lodash" );
|
|
10
|
-
const tools = require( "#tools" );
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Enum for listing all system settings.
|
|
14
|
-
*
|
|
15
|
-
* @readonly
|
|
16
|
-
* @enum {string} Keys of this ENUM are strings.
|
|
17
|
-
* @typedef {string} TiSetting
|
|
18
|
-
*/
|
|
19
|
-
const settingsEnum = tools.enum( {
|
|
20
|
-
AUDITING_LOG_CONSOLE_ENABLED: [ "auditing.logConsoleEnabled", "logConsoleEnabled", "" ],
|
|
21
|
-
AUDITING_LOG_DETAILS: [ "auditing.logDetails", "logDetails", "" ],
|
|
22
|
-
AUDITING_LOG_MIN_LEVEL: [ "auditing.logMinLevel", "logMinLevel", "" ],
|
|
23
|
-
AUDITING_LOG_USES_JSON: [ "auditing.logUsesJSON", "logUsesJSON", "" ],
|
|
24
|
-
GCLOUD_API_KEY: [ "gcloudIntegration.apiKey", "apiKey", "" ],
|
|
25
|
-
GCLOUD_PROJECT_ID: [ "gcloudIntegration.projectID", "projectID", "" ],
|
|
26
|
-
LOCALIZATION_LABELS_PATH: [ "localization.labelsPath", "labelsPath", "" ],
|
|
27
|
-
LOCALIZATION_LANGUAGE: [ "localization.language", "language", "" ],
|
|
28
|
-
MEMORY_CACHE_AUTH_KEY: [ "memoryCache.authKey", "authKey", "" ],
|
|
29
|
-
MEMORY_CACHE_REDIS_DB: [ "memoryCache.redisDB", "redisDB", "" ],
|
|
30
|
-
MEMORY_CACHE_REDIS_HOST: [ "memoryCache.redisHost", "redisHost", "" ],
|
|
31
|
-
MEMORY_CACHE_REDIS_PORT: [ "memoryCache.redisPort", "redisPort", "" ],
|
|
32
|
-
MEMORY_CACHE_RETRY_MAX_ATTEMPTS: [ "memoryCache.retryMaxAttempts", "retryMaxAttempts", "" ],
|
|
33
|
-
MEMORY_CACHE_RETRY_MAX_INTERVAL: [ "memoryCache.retryMaxInterval", "retryMaxInterval", "" ],
|
|
34
|
-
MEMORY_CACHE_USER: [ "memoryCache.user", "user", "" ],
|
|
35
|
-
MESSAGE_EXCHANGE_QUEUE_PREFIX: [ "messageExchange.messageQueuePrefix", "messageQueuePrefix", "" ],
|
|
36
|
-
MESSAGE_EXCHANGE_MESSAGE_STORE: [ "messageExchange.messageStore", "messageStore", "" ],
|
|
37
|
-
MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED: [ "messageExchange.securityHashEnabled", "securityHashEnabled", "" ],
|
|
38
|
-
MESSAGE_EXCHANGE_SECURITY_HASH_KEY: [ "messageExchange.securityHashKey", "securityHashKey", "" ],
|
|
39
|
-
MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME: [ "messageExchange.traceExpirationTime", "traceExpirationTime", "" ],
|
|
40
|
-
MESSAGE_EXCHANGE_TRACE_LOG_ENABLED: [ "messageExchange.traceLogEnabled", "traceLogEnabled", "" ],
|
|
41
|
-
MESSAGE_EXCHANGE_TRACE_REPOSITORY: [ "messageExchange.traceRepository", "traceRepository", "" ],
|
|
42
|
-
SERVICE_EXECUTION_TIMEOUT: [ "serviceConfig.executionTimeout", "executionTimeout", "" ],
|
|
43
|
-
SERVICE_HEALTH_CHECK_ADDRESS: [ "serviceConfig.healthCheckAddress", "healthCheckAddress", "" ],
|
|
44
|
-
SERVICE_HEALTH_CHECK_INTERVAL: [ "serviceConfig.healthCheckInterval", "healthCheckInterval", "" ],
|
|
45
|
-
SERVICE_HEALTH_CHECK_TIMEOUT: [ "serviceConfig.healthCheckTimeout", "healthCheckTimeout", "" ],
|
|
46
|
-
SERVICE_REGISTRY_ADDRESS: [ "serviceConfig.serviceRegistryAddress", "serviceRegistryAddress", "" ],
|
|
47
|
-
OPERATION_MODE: [ "operationMode", "operationMode", "" ]
|
|
48
|
-
} );
|
|
49
|
-
module.exports.setting = settingsEnum;
|
|
50
|
-
|
|
51
|
-
/** @type {SettingsMain} */
|
|
52
|
-
const settings = require( "#settings" );
|
|
53
|
-
|
|
54
|
-
// Override the remaining settings with ENV variables (if provided):
|
|
55
|
-
if ( settings.auditing ) {
|
|
56
|
-
settings.auditing.logConsoleEnabled = ( process.env.TI_AUDITING_LOG_CONSOLE_ENABLED !== undefined ) ? tools.toBool( process.env.TI_AUDITING_LOG_CONSOLE_ENABLED ) : settings.auditing.logConsoleEnabled;
|
|
57
|
-
settings.auditing.logDetails = ( process.env.TI_AUDITING_LOG_DETAILS !== undefined ) ? tools.toBool( process.env.TI_AUDITING_LOG_DETAILS ) : settings.auditing.logDetails;
|
|
58
|
-
settings.auditing.logMinLevel = ( process.env.TI_AUDITING_LOG_MIN_LEVEL !== undefined ) ? process.env.TI_AUDITING_LOG_MIN_LEVEL : settings.auditing.logMinLevel;
|
|
59
|
-
settings.auditing.logUsesJSON = ( process.env.TI_AUDITING_LOG_USES_JSON !== undefined ) ? tools.toBool( process.env.TI_AUDITING_LOG_USES_JSON ) : settings.auditing.logUsesJSON;
|
|
60
|
-
}
|
|
61
|
-
if ( settings.localization ) {
|
|
62
|
-
settings.localization.labelsPath = ( process.env.TI_LOCALIZATION_LABELS_PATH !== undefined )
|
|
63
|
-
? process.env.TI_LOCALIZATION_LABELS_PATH.split( "," ).map( ( p ) => p.trim() ).filter( Boolean )
|
|
64
|
-
: settings.localization.labelsPath;
|
|
65
|
-
settings.localization.language = ( process.env.TI_LOCALIZATION_LANGUAGE !== undefined ) ? process.env.TI_LOCALIZATION_LANGUAGE : settings.localization.language;
|
|
66
|
-
}
|
|
67
|
-
if ( settings.memoryCache ) {
|
|
68
|
-
settings.memoryCache.authKey = ( process.env.TI_MEMORY_CACHE_AUTH_KEY !== undefined ) ? process.env.TI_MEMORY_CACHE_AUTH_KEY : settings.memoryCache.authKey;
|
|
69
|
-
settings.memoryCache.redisDB = ( process.env.TI_MEMORY_CACHE_REDIS_DB !== undefined ) ? Number( process.env.TI_MEMORY_CACHE_REDIS_DB ) : settings.memoryCache.redisDB;
|
|
70
|
-
settings.memoryCache.redisHost = ( process.env.TI_MEMORY_CACHE_REDIS_HOST !== undefined ) ? process.env.TI_MEMORY_CACHE_REDIS_HOST : settings.memoryCache.redisHost;
|
|
71
|
-
settings.memoryCache.redisPort = ( process.env.TI_MEMORY_CACHE_REDIS_PORT !== undefined ) ? Number( process.env.TI_MEMORY_CACHE_REDIS_PORT ) : settings.memoryCache.redisPort;
|
|
72
|
-
settings.memoryCache.retryMaxAttempts = ( process.env.TI_MEMORY_CACHE_RETRY_MAX_ATTEMPTS !== undefined ) ? Number( process.env.TI_MEMORY_CACHE_RETRY_MAX_ATTEMPTS ) : settings.memoryCache.retryMaxAttempts;
|
|
73
|
-
settings.memoryCache.retryMaxInterval = ( process.env.TI_MEMORY_CACHE_RETRY_MAX_INTERVAL !== undefined ) ? Number( process.env.TI_MEMORY_CACHE_RETRY_MAX_INTERVAL ) : settings.memoryCache.retryMaxInterval;
|
|
74
|
-
settings.memoryCache.user = ( process.env.TI_MEMORY_CACHE_USER !== undefined ) ? process.env.TI_MEMORY_CACHE_USER : settings.memoryCache.user;
|
|
75
|
-
}
|
|
76
|
-
if ( settings.messageExchange ) {
|
|
77
|
-
settings.messageExchange.securityHashEnabled = ( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED !== undefined ) ? tools.toBool( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED ) : settings.messageExchange.securityHashEnabled;
|
|
78
|
-
settings.messageExchange.securityHashKey = ( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY !== undefined ) ? process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY : settings.messageExchange.securityHashKey;
|
|
79
|
-
settings.messageExchange.traceLogEnabled = ( process.env.TI_MESSAGE_EXCHANGE_TRACE_LOG_ENABLED !== undefined ) ? tools.toBool( process.env.TI_MESSAGE_EXCHANGE_TRACE_LOG_ENABLED ) : settings.messageExchange.traceLogEnabled;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// Make sure GCloud is enabled before trying to set it up:
|
|
83
|
-
if ( tools.toBool( process.env.TI_GCLOUD_ENABLED ) === true && settings.gcloudIntegration ) {
|
|
84
|
-
settings.gcloudIntegration.apiKey = ( process.env.TI_GCLOUD_API_KEY !== undefined ) ? process.env.TI_GCLOUD_API_KEY : settings.gcloudIntegration.apiKey;
|
|
85
|
-
settings.gcloudIntegration.projectID = ( process.env.TI_GCLOUD_PROJECT_ID !== undefined ) ? process.env.TI_GCLOUD_PROJECT_ID : settings.gcloudIntegration.projectID;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
settings.operationMode = process.env.NODE_ENV || settings.operationMode;
|
|
89
|
-
|
|
90
|
-
// Prevent further modifications to the settings object:
|
|
91
|
-
tools.deepFreeze( settings );
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* A standard getter method for fetching a setting.
|
|
95
|
-
*
|
|
96
|
-
* @method
|
|
97
|
-
* @param {string|TiSetting} setting Specifies either a dot-separated JSON path of the setting, or is a Setting from the settings enum.
|
|
98
|
-
* @param {*} [defaultValue] The default value to be returned if the setting is not found in the current configuration.
|
|
99
|
-
* @returns {*}
|
|
100
|
-
* @public
|
|
101
|
-
*/
|
|
102
|
-
module.exports.getSetting = ( setting, defaultValue ) => {
|
|
103
|
-
return _.get( settings, setting, defaultValue );
|
|
1
|
+
/*
|
|
2
|
+
* The ti-engine is an open source, free to use—both for personal and commercial projects—framework for the creation of microservice-based solutions using node.js.
|
|
3
|
+
* Copyright © 2021-2026 Boris Kostadinov <kostadinov.boris@gmail.com>
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
|
5
|
+
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
|
6
|
+
* You should have received a copy of the GNU General Public License along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const _ = require( "lodash" );
|
|
10
|
+
const tools = require( "#tools" );
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Enum for listing all system settings.
|
|
14
|
+
*
|
|
15
|
+
* @readonly
|
|
16
|
+
* @enum {string} Keys of this ENUM are strings.
|
|
17
|
+
* @typedef {string} TiSetting
|
|
18
|
+
*/
|
|
19
|
+
const settingsEnum = tools.enum( {
|
|
20
|
+
AUDITING_LOG_CONSOLE_ENABLED: [ "auditing.logConsoleEnabled", "logConsoleEnabled", "" ],
|
|
21
|
+
AUDITING_LOG_DETAILS: [ "auditing.logDetails", "logDetails", "" ],
|
|
22
|
+
AUDITING_LOG_MIN_LEVEL: [ "auditing.logMinLevel", "logMinLevel", "" ],
|
|
23
|
+
AUDITING_LOG_USES_JSON: [ "auditing.logUsesJSON", "logUsesJSON", "" ],
|
|
24
|
+
GCLOUD_API_KEY: [ "gcloudIntegration.apiKey", "apiKey", "" ],
|
|
25
|
+
GCLOUD_PROJECT_ID: [ "gcloudIntegration.projectID", "projectID", "" ],
|
|
26
|
+
LOCALIZATION_LABELS_PATH: [ "localization.labelsPath", "labelsPath", "" ],
|
|
27
|
+
LOCALIZATION_LANGUAGE: [ "localization.language", "language", "" ],
|
|
28
|
+
MEMORY_CACHE_AUTH_KEY: [ "memoryCache.authKey", "authKey", "" ],
|
|
29
|
+
MEMORY_CACHE_REDIS_DB: [ "memoryCache.redisDB", "redisDB", "" ],
|
|
30
|
+
MEMORY_CACHE_REDIS_HOST: [ "memoryCache.redisHost", "redisHost", "" ],
|
|
31
|
+
MEMORY_CACHE_REDIS_PORT: [ "memoryCache.redisPort", "redisPort", "" ],
|
|
32
|
+
MEMORY_CACHE_RETRY_MAX_ATTEMPTS: [ "memoryCache.retryMaxAttempts", "retryMaxAttempts", "" ],
|
|
33
|
+
MEMORY_CACHE_RETRY_MAX_INTERVAL: [ "memoryCache.retryMaxInterval", "retryMaxInterval", "" ],
|
|
34
|
+
MEMORY_CACHE_USER: [ "memoryCache.user", "user", "" ],
|
|
35
|
+
MESSAGE_EXCHANGE_QUEUE_PREFIX: [ "messageExchange.messageQueuePrefix", "messageQueuePrefix", "" ],
|
|
36
|
+
MESSAGE_EXCHANGE_MESSAGE_STORE: [ "messageExchange.messageStore", "messageStore", "" ],
|
|
37
|
+
MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED: [ "messageExchange.securityHashEnabled", "securityHashEnabled", "" ],
|
|
38
|
+
MESSAGE_EXCHANGE_SECURITY_HASH_KEY: [ "messageExchange.securityHashKey", "securityHashKey", "" ],
|
|
39
|
+
MESSAGE_EXCHANGE_TRACE_EXPIRATION_TIME: [ "messageExchange.traceExpirationTime", "traceExpirationTime", "" ],
|
|
40
|
+
MESSAGE_EXCHANGE_TRACE_LOG_ENABLED: [ "messageExchange.traceLogEnabled", "traceLogEnabled", "" ],
|
|
41
|
+
MESSAGE_EXCHANGE_TRACE_REPOSITORY: [ "messageExchange.traceRepository", "traceRepository", "" ],
|
|
42
|
+
SERVICE_EXECUTION_TIMEOUT: [ "serviceConfig.executionTimeout", "executionTimeout", "" ],
|
|
43
|
+
SERVICE_HEALTH_CHECK_ADDRESS: [ "serviceConfig.healthCheckAddress", "healthCheckAddress", "" ],
|
|
44
|
+
SERVICE_HEALTH_CHECK_INTERVAL: [ "serviceConfig.healthCheckInterval", "healthCheckInterval", "" ],
|
|
45
|
+
SERVICE_HEALTH_CHECK_TIMEOUT: [ "serviceConfig.healthCheckTimeout", "healthCheckTimeout", "" ],
|
|
46
|
+
SERVICE_REGISTRY_ADDRESS: [ "serviceConfig.serviceRegistryAddress", "serviceRegistryAddress", "" ],
|
|
47
|
+
OPERATION_MODE: [ "operationMode", "operationMode", "" ]
|
|
48
|
+
} );
|
|
49
|
+
module.exports.setting = settingsEnum;
|
|
50
|
+
|
|
51
|
+
/** @type {SettingsMain} */
|
|
52
|
+
const settings = require( "#settings" );
|
|
53
|
+
|
|
54
|
+
// Override the remaining settings with ENV variables (if provided):
|
|
55
|
+
if ( settings.auditing ) {
|
|
56
|
+
settings.auditing.logConsoleEnabled = ( process.env.TI_AUDITING_LOG_CONSOLE_ENABLED !== undefined ) ? tools.toBool( process.env.TI_AUDITING_LOG_CONSOLE_ENABLED ) : settings.auditing.logConsoleEnabled;
|
|
57
|
+
settings.auditing.logDetails = ( process.env.TI_AUDITING_LOG_DETAILS !== undefined ) ? tools.toBool( process.env.TI_AUDITING_LOG_DETAILS ) : settings.auditing.logDetails;
|
|
58
|
+
settings.auditing.logMinLevel = ( process.env.TI_AUDITING_LOG_MIN_LEVEL !== undefined ) ? process.env.TI_AUDITING_LOG_MIN_LEVEL : settings.auditing.logMinLevel;
|
|
59
|
+
settings.auditing.logUsesJSON = ( process.env.TI_AUDITING_LOG_USES_JSON !== undefined ) ? tools.toBool( process.env.TI_AUDITING_LOG_USES_JSON ) : settings.auditing.logUsesJSON;
|
|
60
|
+
}
|
|
61
|
+
if ( settings.localization ) {
|
|
62
|
+
settings.localization.labelsPath = ( process.env.TI_LOCALIZATION_LABELS_PATH !== undefined )
|
|
63
|
+
? process.env.TI_LOCALIZATION_LABELS_PATH.split( "," ).map( ( p ) => p.trim() ).filter( Boolean )
|
|
64
|
+
: settings.localization.labelsPath;
|
|
65
|
+
settings.localization.language = ( process.env.TI_LOCALIZATION_LANGUAGE !== undefined ) ? process.env.TI_LOCALIZATION_LANGUAGE : settings.localization.language;
|
|
66
|
+
}
|
|
67
|
+
if ( settings.memoryCache ) {
|
|
68
|
+
settings.memoryCache.authKey = ( process.env.TI_MEMORY_CACHE_AUTH_KEY !== undefined ) ? process.env.TI_MEMORY_CACHE_AUTH_KEY : settings.memoryCache.authKey;
|
|
69
|
+
settings.memoryCache.redisDB = ( process.env.TI_MEMORY_CACHE_REDIS_DB !== undefined ) ? Number( process.env.TI_MEMORY_CACHE_REDIS_DB ) : settings.memoryCache.redisDB;
|
|
70
|
+
settings.memoryCache.redisHost = ( process.env.TI_MEMORY_CACHE_REDIS_HOST !== undefined ) ? process.env.TI_MEMORY_CACHE_REDIS_HOST : settings.memoryCache.redisHost;
|
|
71
|
+
settings.memoryCache.redisPort = ( process.env.TI_MEMORY_CACHE_REDIS_PORT !== undefined ) ? Number( process.env.TI_MEMORY_CACHE_REDIS_PORT ) : settings.memoryCache.redisPort;
|
|
72
|
+
settings.memoryCache.retryMaxAttempts = ( process.env.TI_MEMORY_CACHE_RETRY_MAX_ATTEMPTS !== undefined ) ? Number( process.env.TI_MEMORY_CACHE_RETRY_MAX_ATTEMPTS ) : settings.memoryCache.retryMaxAttempts;
|
|
73
|
+
settings.memoryCache.retryMaxInterval = ( process.env.TI_MEMORY_CACHE_RETRY_MAX_INTERVAL !== undefined ) ? Number( process.env.TI_MEMORY_CACHE_RETRY_MAX_INTERVAL ) : settings.memoryCache.retryMaxInterval;
|
|
74
|
+
settings.memoryCache.user = ( process.env.TI_MEMORY_CACHE_USER !== undefined ) ? process.env.TI_MEMORY_CACHE_USER : settings.memoryCache.user;
|
|
75
|
+
}
|
|
76
|
+
if ( settings.messageExchange ) {
|
|
77
|
+
settings.messageExchange.securityHashEnabled = ( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED !== undefined ) ? tools.toBool( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED ) : settings.messageExchange.securityHashEnabled;
|
|
78
|
+
settings.messageExchange.securityHashKey = ( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY !== undefined ) ? process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY : settings.messageExchange.securityHashKey;
|
|
79
|
+
settings.messageExchange.traceLogEnabled = ( process.env.TI_MESSAGE_EXCHANGE_TRACE_LOG_ENABLED !== undefined ) ? tools.toBool( process.env.TI_MESSAGE_EXCHANGE_TRACE_LOG_ENABLED ) : settings.messageExchange.traceLogEnabled;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Make sure GCloud is enabled before trying to set it up:
|
|
83
|
+
if ( tools.toBool( process.env.TI_GCLOUD_ENABLED ) === true && settings.gcloudIntegration ) {
|
|
84
|
+
settings.gcloudIntegration.apiKey = ( process.env.TI_GCLOUD_API_KEY !== undefined ) ? process.env.TI_GCLOUD_API_KEY : settings.gcloudIntegration.apiKey;
|
|
85
|
+
settings.gcloudIntegration.projectID = ( process.env.TI_GCLOUD_PROJECT_ID !== undefined ) ? process.env.TI_GCLOUD_PROJECT_ID : settings.gcloudIntegration.projectID;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
settings.operationMode = process.env.NODE_ENV || settings.operationMode;
|
|
89
|
+
|
|
90
|
+
// Prevent further modifications to the settings object:
|
|
91
|
+
tools.deepFreeze( settings );
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* A standard getter method for fetching a setting.
|
|
95
|
+
*
|
|
96
|
+
* @method
|
|
97
|
+
* @param {string|TiSetting} setting Specifies either a dot-separated JSON path of the setting, or is a Setting from the settings enum.
|
|
98
|
+
* @param {*} [defaultValue] The default value to be returned if the setting is not found in the current configuration.
|
|
99
|
+
* @returns {*}
|
|
100
|
+
* @public
|
|
101
|
+
*/
|
|
102
|
+
module.exports.getSetting = ( setting, defaultValue ) => {
|
|
103
|
+
return _.get( settings, setting, defaultValue );
|
|
104
104
|
};
|
package/utils/exceptions.js
CHANGED
|
@@ -60,7 +60,8 @@ const exceptionCodeEnum = tools.enum( {
|
|
|
60
60
|
E_WEB_INVALID_REQUEST_CONTENT_ENCODING: [ 4009, "invalid request content encoding", "The request content encoding is not recognized or not supported." ],
|
|
61
61
|
/** Application exceptions - codes under 5xxx */
|
|
62
62
|
E_APP_RESOURCE_NOT_FOUND: [ 5004, "resource not found", "The requested resource cannot be found. See details for more information." ],
|
|
63
|
-
E_APP_SERVICE_ERROR: [ 5005, "app service error", "The application service encountered an error. See details for more information." ]
|
|
63
|
+
E_APP_SERVICE_ERROR: [ 5005, "app service error", "The application service encountered an error. See details for more information." ],
|
|
64
|
+
E_APP_RESOURCE_ALREADY_EXISTS: [ 5006, "resource already exists", "The resource cannot be created because it already exists. See details for more information." ]
|
|
64
65
|
} );
|
|
65
66
|
module.exports.exceptionCode = exceptionCodeEnum;
|
|
66
67
|
|
package/utils/tools.js
CHANGED
|
@@ -499,6 +499,26 @@ module.exports.decomposeJSON = ( input ) => {
|
|
|
499
499
|
return decomposed;
|
|
500
500
|
};
|
|
501
501
|
|
|
502
|
+
/**
|
|
503
|
+
* Constant-time string comparison. Mirrors the web-framework safe-compare idiom: coerces inputs to
|
|
504
|
+
* utf8 buffers, short-circuits on length mismatch, and never throws on hostile/non-string input.
|
|
505
|
+
*
|
|
506
|
+
* @method
|
|
507
|
+
* @param {*} a
|
|
508
|
+
* @param {*} b
|
|
509
|
+
* @returns {boolean} True only when both inputs coerce to equal-length, byte-identical strings.
|
|
510
|
+
* @public
|
|
511
|
+
*/
|
|
512
|
+
module.exports.constantTimeEquals = ( a, b ) => {
|
|
513
|
+
try {
|
|
514
|
+
const ba = Buffer.from( String( a || "" ), "utf8" );
|
|
515
|
+
const bb = Buffer.from( String( b || "" ), "utf8" );
|
|
516
|
+
return ( ba.length !== bb.length ) ? false : crypto.timingSafeEqual( ba, bb );
|
|
517
|
+
} catch {
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
|
|
502
522
|
/**
|
|
503
523
|
* Used to create retry policy for the execution of an operation.
|
|
504
524
|
*
|