@ti-engine/core 1.0.10 → 1.0.11

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 ADDED
@@ -0,0 +1,7 @@
1
+ ##Version 1.0.11
2
+ * feat(message exchange): implement message tampering and insertion protection
3
+ * feat(config): provide option to turn on/off the message tampering and insertion protection
4
+ * feat(config): provide option to turn on/off message tracing
5
+ * feat: upgrade all used packages to their latest versions
6
+ * refactor(config)!: rename all environment variables that set configuration settings to match their related setting
7
+ * docs: add a change log
package/README.md CHANGED
@@ -95,7 +95,7 @@ The architectural approach for the **ti-engine** is done in _tiers_ with lower t
95
95
 
96
96
  There are three general tiers in the **ti-engine**:
97
97
  1. message exchange
98
- 2. service interface
98
+ 2. service domains
99
99
  3. solution implementation
100
100
 
101
101
  See the following sections for more information on each of them.
package/bin/settings.json CHANGED
@@ -20,6 +20,8 @@
20
20
  "messageExchange": {
21
21
  "messageQueuePrefix": "ti:messages:",
22
22
  "messageStore": "ti:messages:store",
23
+ "securityHashEnabled": true,
24
+ "securityHashKey": "23e7bdc7-a793-41f9-856e-6760332f0c73",
23
25
  "traceLogEnabled": true
24
26
  },
25
27
  "serviceConfig": {
@@ -74,7 +74,7 @@ class DefaultMessageReceiver extends MessageReceiver {
74
74
  onReceive() {
75
75
  return new Promise( ( resolve, reject ) => {
76
76
  this.#memoryCache.receiveMessage( this.receiveQueue ).then( ( lightweightMessage ) => {
77
- return this.#memoryCache.retrieveMessagePayload( lightweightMessage, config.getSetting( config.setting.MESSAGE_EXCHANGE_STORE ) );
77
+ return this.#memoryCache.retrieveMessagePayload( lightweightMessage, config.getSetting( config.setting.MESSAGE_EXCHANGE_MESSAGE_STORE ) );
78
78
  } ).then( ( message ) => {
79
79
  resolve( message );
80
80
  } ).catch( ( error ) => {
@@ -42,7 +42,7 @@ class DefaultMessageSender extends MessageSender {
42
42
  */
43
43
  onSend( message, queue ) {
44
44
  return new Promise( ( resolve, reject ) => {
45
- this.#memoryCache.storeMessagePayload( message.payload, config.getSetting( config.setting.MESSAGE_EXCHANGE_STORE ) ).then( ( storeID ) => {
45
+ this.#memoryCache.storeMessagePayload( message.payload, config.getSetting( config.setting.MESSAGE_EXCHANGE_MESSAGE_STORE ) ).then( ( storeID ) => {
46
46
  let lightweightMessage = _.cloneDeep( message );
47
47
  lightweightMessage.payload = storeID;
48
48
  return this.#memoryCache.sendMessage( lightweightMessage, queue );
@@ -25,8 +25,9 @@ const messageTracer = require( "#message-tracer" );
25
25
  * @property {string} chainID Unique identifier of the message chain if the message is part of one.
26
26
  * @property {number} chainLevel The node level of this message in the message chain tree.
27
27
  * @property {MessageDestination} destination The destination of the message.
28
+ * @property {string} [hash] Security hash for the message if the mechanism is enabled.
28
29
  * @property {string} messageID Unique message identifier.
29
- * @property {Object|string|undefined} payload The message contents to be processed in destination. If string, it is ID of the payload in the memory cache instead.
30
+ * @property {Object|string|undefined} payload The message contents to be processed in destination. If string it is ID of the payload in the memory cache instead.
30
31
  * Note that if this is not an Object or a string, there is no guarantee that it will be delivered in the same/proper format!
31
32
  * @property {MessageSource} source The source of the message.
32
33
  */
@@ -5,8 +5,11 @@
5
5
 
6
6
  const ConnectionObserver = require( "#connection-observer" );
7
7
  const _ = require( "lodash" );
8
+ const blake2 = require( "blake2" );
8
9
  const exceptions = require( "#exceptions" );
9
10
  const logger = require( "#logger" );
11
+ const tools = require( "#tools" );
12
+ const config = require( "#config" );
10
13
 
11
14
  /**
12
15
  * An abstract class that defines a basic message handler behavior.
@@ -98,6 +101,21 @@ class MessageHandler extends ConnectionObserver {
98
101
  return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_METHOD_CALL, { name: this.constructor.name + "." + this.disable.name } ) );
99
102
  }
100
103
 
104
+ /**
105
+ * Used to create a security hash from the message.
106
+ *
107
+ * @method
108
+ * @param {Message} message
109
+ * @returns {string}
110
+ * @public
111
+ */
112
+ createMessageHash( message ) {
113
+ let transformed = tools.decomposeJSON( tools.decycle( message ) );
114
+ let hash = blake2.createKeyedHash( "blake2b", Buffer.from( config.getSetting( config.setting.MESSAGE_EXCHANGE_SECURITY_HASH_KEY ) ) );
115
+ hash.update( Buffer.from( transformed ) );
116
+ return hash.digest( "hex" );
117
+ }
118
+
101
119
  /**
102
120
  * Used to register a new {@link MessageObserver} for events related to the messages passing through this handler.
103
121
  *
@@ -6,6 +6,7 @@
6
6
  const MessageHandler = require( "#message-handler" );
7
7
  const logger = require( "#logger" );
8
8
  const exceptions = require( "#exceptions" );
9
+ const config = require( "#config" );
9
10
 
10
11
  /**
11
12
  * An abstract class that defines a basic message receiver behavior.
@@ -84,6 +85,8 @@ class MessageReceiver extends MessageHandler {
84
85
  */
85
86
  receive() {
86
87
  this.onReceive().then( ( message ) => {
88
+ return this.#postReceive( message );
89
+ } ).then( ( message ) => {
87
90
  this.onMessage( message );
88
91
  } ).catch( ( error ) => {
89
92
  logger.log( `Error while trying to receive the next pending message from memory cache in receiver '${ this.connectionIdentifier }'! Resuming operation...`, logger.logSeverity.ERROR, error );
@@ -108,6 +111,37 @@ class MessageReceiver extends MessageHandler {
108
111
  return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_GEN_ABSTRACT_METHOD_CALL, { name: this.constructor.name + "." + this.onReceive.name } ) );
109
112
  }
110
113
 
114
+ /* Private interface */
115
+
116
+ /**
117
+ * Used to process the received message before providing it to any {@link MessageObserver}.
118
+ *
119
+ * @method
120
+ * @param {Message} message
121
+ * @returns {Promise<Message>}
122
+ * @private
123
+ */
124
+ #postReceive( message ) {
125
+ return new Promise( ( resolve, reject ) => {
126
+ if ( config.getSetting( config.setting.MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED ) === true ) {
127
+ let receivedHash = message.hash;
128
+ delete message.hash;
129
+ let currentHash = this.createMessageHash( message );
130
+ if ( receivedHash && receivedHash === currentHash ) {
131
+ resolve( message );
132
+ } else {
133
+ reject( exceptions.raise( exceptions.exceptionCode.E_SEC_MESSAGE_TAMPERING_DETECTED, {
134
+ messageID: message.messageID,
135
+ receivedHash: receivedHash,
136
+ currentHash: currentHash
137
+ } ) );
138
+ }
139
+ } else {
140
+ resolve( message );
141
+ }
142
+ } );
143
+ }
144
+
111
145
  }
112
146
 
113
147
  module.exports = MessageReceiver;
@@ -5,6 +5,7 @@
5
5
 
6
6
  const MessageHandler = require( "#message-handler" );
7
7
  const exceptions = require( "#exceptions" );
8
+ const config = require( "#config" );
8
9
 
9
10
  /**
10
11
  * An abstract class that defines a basic message sender behavior.
@@ -70,7 +71,7 @@ class MessageSender extends MessageHandler {
70
71
  */
71
72
  send( message, queue ) {
72
73
  return new Promise( ( resolve, reject ) => {
73
- this.#preSend().then( () => {
74
+ this.#preSend( message ).then( ( message ) => {
74
75
  return this.onSend( message, queue );
75
76
  } ).then( () => {
76
77
  return this.#postSend();
@@ -106,12 +107,17 @@ class MessageSender extends MessageHandler {
106
107
  * Used to do pre-send verifications and checks.
107
108
  *
108
109
  * @method
109
- * @returns {Promise}
110
+ * @param {Message} message
111
+ * @returns {Promise<Message>}
110
112
  * @private
111
113
  */
112
- #preSend() {
114
+ #preSend( message ) {
113
115
  if ( this.isAvailable === true ) {
114
- return Promise.resolve();
116
+ if ( config.getSetting( config.setting.MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED ) === true ) {
117
+ message.hash = this.createMessageHash( message );
118
+ }
119
+
120
+ return Promise.resolve( message );
115
121
  } else {
116
122
  return Promise.reject( exceptions.raise( exceptions.exceptionCode.E_COM_MESSAGE_SENDER_UNAVAILABLE ) );
117
123
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ti-engine/core",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
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
  "author": "Boris Kostadinov <kostadinov.boris@gmail.com>",
6
6
  "license": "ISC",
@@ -41,12 +41,12 @@
41
41
  "#tools": "./utils/tools.js"
42
42
  },
43
43
  "dependencies": {
44
- "blake2": "^4.0.2",
45
- "dotenv": "^10.0.0",
44
+ "blake2": "^4.1.0",
45
+ "dotenv": "^14.1.0",
46
46
  "fs-extra": "^10.0.0",
47
47
  "lodash": "^4.17.21",
48
48
  "node-schedule": "^2.1.0",
49
- "ioredis": "^4.28.2"
49
+ "ioredis": "^4.28.3"
50
50
  },
51
51
  "optionalDependencies": {
52
52
  "@google-cloud/error-reporting": "^2.0.4"
package/utils/config.js CHANGED
@@ -20,13 +20,16 @@ const tools = require( "#tools" );
20
20
  * @property {EnvironmentVariable} env.TI_INSTANCE_CONFIG
21
21
  * @property {EnvironmentVariable} env.TI_INSTANCE_ID
22
22
  * @property {EnvironmentVariable} env.TI_INSTANCE_NAME
23
- * @property {EnvironmentVariable} env.TI_LOG_CONSOLE_ENABLED
24
- * @property {EnvironmentVariable} env.TI_LOG_MIN_LEVEL
25
- * @property {EnvironmentVariable} env.TI_LOG_USED_JSON
23
+ * @property {EnvironmentVariable} env.TI_AUDITING_LOG_CONSOLE_ENABLED
24
+ * @property {EnvironmentVariable} env.TI_AUDITING_LOG_MIN_LEVEL
25
+ * @property {EnvironmentVariable} env.TI_AUDITING_LOG_USES_JSON
26
26
  * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_AUTH_KEY
27
- * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_DB
28
- * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_HOST
29
- * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_PORT
27
+ * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_REDIS_DB
28
+ * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_REDIS_HOST
29
+ * @property {EnvironmentVariable} env.TI_MEMORY_CACHE_REDIS_PORT
30
+ * @property {EnvironmentVariable} env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED
31
+ * @property {EnvironmentVariable} env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY
32
+ * @property {EnvironmentVariable} env.TI_MESSAGE_EXCHANGE_TRACE_LOG_ENABLED
30
33
  * @property {EnvironmentVariable} env.TI_OPERATION_MODE
31
34
  */
32
35
 
@@ -70,6 +73,8 @@ const tools = require( "#tools" );
70
73
  * @typedef {Object} SettingsMessageExchange
71
74
  * @property {string} messageQueuePrefix
72
75
  * @property {string} messageStore
76
+ * @property {boolean} securityHashEnabled
77
+ * @property {string} securityHashKey
73
78
  * @property {boolean} traceLogEnabled
74
79
  */
75
80
 
@@ -100,7 +105,9 @@ let settingsEnum = tools.enum( {
100
105
  MEMORY_CACHE_REDIS_HOST: [ "memoryCache.redisHost", "redisHost", "" ],
101
106
  MEMORY_CACHE_REDIS_PORT: [ "memoryCache.redisPort", "redisPort", "" ],
102
107
  MESSAGE_EXCHANGE_QUEUE_PREFIX: [ "messageExchange.messageQueuePrefix", "messageQueuePrefix", "" ],
103
- MESSAGE_EXCHANGE_STORE: [ "messageExchange.messageStore", "messageStore", "" ],
108
+ MESSAGE_EXCHANGE_MESSAGE_STORE: [ "messageExchange.messageStore", "messageStore", "" ],
109
+ MESSAGE_EXCHANGE_SECURITY_HASH_ENABLED: [ "messageExchange.securityHashEnabled", "securityHashEnabled", "" ],
110
+ MESSAGE_EXCHANGE_SECURITY_HASH_KEY: [ "messageExchange.securityHashKey", "securityHashKey", "" ],
104
111
  MESSAGE_EXCHANGE_TRACE_LOG_ENABLED: [ "messageExchange.traceLogEnabled", "traceLogEnabled", "" ],
105
112
  SERVICE_EXECUTION_TIMEOUT: [ "serviceConfig.executionTimeout", "executionTimeout", "" ],
106
113
  SERVICE_HEALTH_CHECK_ADDRESS: [ "serviceConfig.healthCheckAddress", "healthCheckAddress", "" ],
@@ -120,18 +127,23 @@ const settings = require( "#settings" );
120
127
 
121
128
  // override remaining settings with ENV variables (if provided):
122
129
  if ( settings.auditing ) {
123
- settings.auditing.logMinLevel = ( process.env.TI_LOG_MIN_LEVEL !== undefined ) ? process.env.TI_LOG_MIN_LEVEL : settings.auditing.logMinLevel;
124
- settings.auditing.logConsoleEnabled = ( process.env.TI_LOG_CONSOLE_ENABLED !== undefined ) ? tools.toBool( process.env.TI_LOG_CONSOLE_ENABLED ) : settings.auditing.logConsoleEnabled;
125
- settings.auditing.logUsesJSON = ( process.env.TI_LOG_USED_JSON !== undefined ) ? tools.toBool( process.env.TI_LOG_USED_JSON ) : settings.auditing.logUsesJSON;
130
+ settings.auditing.logMinLevel = ( process.env.TI_AUDITING_LOG_MIN_LEVEL !== undefined ) ? process.env.TI_AUDITING_LOG_MIN_LEVEL : settings.auditing.logMinLevel;
131
+ settings.auditing.logConsoleEnabled = ( process.env.TI_AUDITING_LOG_CONSOLE_ENABLED !== undefined ) ? tools.toBool( process.env.TI_AUDITING_LOG_CONSOLE_ENABLED ) : settings.auditing.logConsoleEnabled;
132
+ settings.auditing.logUsesJSON = ( process.env.TI_AUDITING_LOG_USES_JSON !== undefined ) ? tools.toBool( process.env.TI_AUDITING_LOG_USES_JSON ) : settings.auditing.logUsesJSON;
126
133
  }
127
134
  if ( settings.memoryCache ) {
128
135
  settings.memoryCache.authKey = ( process.env.TI_MEMORY_CACHE_AUTH_KEY !== undefined ) ? process.env.TI_MEMORY_CACHE_AUTH_KEY : settings.memoryCache.authKey;
129
- settings.memoryCache.redisDB = ( process.env.TI_MEMORY_CACHE_DB !== undefined ) ? process.env.TI_MEMORY_CACHE_DB : settings.memoryCache.redisDB;
130
- settings.memoryCache.redisHost = ( process.env.TI_MEMORY_CACHE_HOST !== undefined ) ? process.env.TI_MEMORY_CACHE_HOST : settings.memoryCache.redisHost;
131
- settings.memoryCache.redisPort = ( process.env.TI_MEMORY_CACHE_PORT !== undefined ) ? process.env.TI_MEMORY_CACHE_PORT : settings.memoryCache.redisPort;
136
+ settings.memoryCache.redisDB = ( process.env.TI_MEMORY_CACHE_REDIS_DB !== undefined ) ? process.env.TI_MEMORY_CACHE_REDIS_DB : settings.memoryCache.redisDB;
137
+ settings.memoryCache.redisHost = ( process.env.TI_MEMORY_CACHE_REDIS_HOST !== undefined ) ? process.env.TI_MEMORY_CACHE_REDIS_HOST : settings.memoryCache.redisHost;
138
+ settings.memoryCache.redisPort = ( process.env.TI_MEMORY_CACHE_REDIS_PORT !== undefined ) ? process.env.TI_MEMORY_CACHE_REDIS_PORT : settings.memoryCache.redisPort;
139
+ }
140
+ if ( settings.messageExchange ) {
141
+ 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;
142
+ settings.messageExchange.securityHashKey = ( process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY !== undefined ) ? process.env.TI_MESSAGE_EXCHANGE_SECURITY_HASH_KEY : settings.messageExchange.securityHashKey;
143
+ 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;
132
144
  }
133
145
 
134
- // make sure GCloud is enabled before trying to setup it:
146
+ // make sure GCloud is enabled before trying to set it up:
135
147
  if ( process.env.TI_GCLOUD_ENABLED === true && settings.gcloudIntegration ) {
136
148
  settings.gcloudIntegration.apiKey = ( process.env.TI_GCLOUD_API_KEY !== undefined ) ? process.env.TI_GCLOUD_API_KEY : settings.gcloudIntegration.apiKey;
137
149
  settings.gcloudIntegration.projectID = ( process.env.TI_GCLOUD_PROJECT_ID !== undefined ) ? process.env.TI_GCLOUD_PROJECT_ID : settings.gcloudIntegration.projectID;
@@ -25,6 +25,7 @@ let exceptionCodeEnum = tools.enum( {
25
25
  E_SEC_INVALID_AUTH_TOKEN: [ 2000, "invalid auth token", "Invalid authorization token provided." ],
26
26
  E_SEC_INVALID_EXPIRED_SESSION: [ 2001, "invalid or expired session", "Invalid or expired session encountered." ],
27
27
  E_SEC_UNAUTHORIZED_ACCESS: [ 2002, "unauthorized access", "Attempt for unauthorized access detected." ],
28
+ E_SEC_MESSAGE_TAMPERING_DETECTED: [ 2003, "message tampering detected", "The system detected tampering with the message received via message exchange." ],
28
29
  /** Cross-Application Communication exceptions - codes under 3xxx */
29
30
  E_COM_GENERAL_ERROR: [ 3000, "general communication error", "General error during cross-application communication." ],
30
31
  E_COM_MESSAGE_SENDER_UNAVAILABLE: [ 3001, "message sender unavailable", "The message sender instance is currently unavailable." ],