node-red-contrib-aws-sqs-variable 1.0.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/README.md ADDED
@@ -0,0 +1,179 @@
1
+ # node-red-contrib-aws-sqs-variable
2
+
3
+ A production-ready Node-RED node for AWS SQS operations that sends and receives messages with flexible credential handling.
4
+
5
+ ## Features
6
+
7
+ - ๐Ÿ“ฌ Send messages to AWS SQS
8
+ - ๐Ÿ“ฅ Receive messages from AWS SQS
9
+ - ๐Ÿ—๏ธ Flexible credential configuration (IAM roles, direct credentials, context variables)
10
+ - ๐ŸŽฏ TypedInput support for dynamic Queue URL and payload
11
+ - ๐Ÿ”„ FIFO support with Message Group and Deduplication IDs
12
+ - ๐Ÿงพ Optional JSON parsing for received messages
13
+ - โฑ๏ธ Built-in polling mode with status indicator
14
+ - ๐Ÿ›ก๏ธ Production-ready with comprehensive error handling
15
+
16
+ ## Installation
17
+
18
+ Run the following command in your Node-RED user directory (typically `~/.node-red`):
19
+
20
+ ```bash
21
+ npm install node-red-contrib-aws-sqs-variable
22
+ ```
23
+
24
+ After installation, restart Node-RED to load the new nodes.
25
+
26
+ ## Configuration
27
+
28
+ ### AWS Configuration Node
29
+
30
+ The module uses a configuration node that supports multiple authentication methods:
31
+
32
+ #### IAM Role Authentication (Recommended)
33
+ - โœ… Use when running on EC2 instances with IAM roles
34
+ - โœ… No credentials needed in Node-RED
35
+ - โœ… Automatic credential rotation
36
+
37
+ #### Access Key Authentication
38
+ Supports multiple credential sources:
39
+
40
+ - **String**: Stored securely in Node-RED credentials (encrypted)
41
+ - **Flow Context**: Retrieved from flow context variables
42
+ - **Global Context**: Retrieved from global context variables
43
+ - **Environment Variables**: Retrieved from environment variables
44
+
45
+ ## Usage
46
+
47
+ ### Basic Usage
48
+
49
+ 1. **Create AWS Configuration**
50
+ - Add an "aws-sqs-config" node
51
+ - Configure your AWS region and credentials
52
+
53
+ 2. **Add AWS SQS Node**
54
+ - Drag "aws-sqs" node to your flow
55
+ - Select your AWS configuration
56
+ - Choose operation: Send or Receive
57
+
58
+ ### Send Messages
59
+
60
+ Configure:
61
+ - **Queue URL** (string, msg, flow, global, env)
62
+ - **Message Body** (string, msg, flow, global, env)
63
+
64
+ Optional for FIFO:
65
+ - **Message Group ID** (required for FIFO)
66
+ - **Deduplication ID** (optional if content-based deduplication is enabled)
67
+
68
+ ### Receive Messages
69
+
70
+ Configure:
71
+ - **Queue URL**
72
+ - **Max Messages** (1-10)
73
+ - **Wait Time (s)** (0-20, long polling)
74
+ - **Visibility Timeout (s)**
75
+ - **Delete After Receive**
76
+ - **Parse JSON Body** (optional)
77
+
78
+ Polling:
79
+ - Enable **Polling** to receive messages on a schedule
80
+ - Set **Poll Interval (s)**
81
+
82
+ ## Input
83
+
84
+ ### Message Properties
85
+
86
+ - `msg.queueUrl` (optional): Queue URL if not configured in node
87
+ - `msg.messageGroupId` (optional): FIFO Message Group ID
88
+ - `msg.messageDeduplicationId` (optional): FIFO Deduplication ID
89
+
90
+ ### Example Input
91
+
92
+ ```javascript
93
+ msg = {
94
+ queueUrl: "https://sqs.eu-central-1.amazonaws.com/123456789012/my-queue",
95
+ payload: { message: "Hello!" }
96
+ }
97
+ ```
98
+
99
+ ## Output
100
+
101
+ ### Send Success Response
102
+
103
+ ```javascript
104
+ msg = {
105
+ payload: {
106
+ messageId: "abcd-1234",
107
+ md5OfMessageBody: "e99a18c428cb38d5f260853678922e03"
108
+ }
109
+ }
110
+ ```
111
+
112
+ ### Receive Success Response
113
+
114
+ ```javascript
115
+ msg = {
116
+ payload: [
117
+ {
118
+ MessageId: "...",
119
+ ReceiptHandle: "...",
120
+ Body: "raw string or parsed JSON when enabled"
121
+ }
122
+ ]
123
+ }
124
+ ```
125
+
126
+ ### Error Response
127
+
128
+ ```javascript
129
+ msg = {
130
+ payload: {
131
+ error: "Error message"
132
+ }
133
+ }
134
+ ```
135
+
136
+ ## FIFO Notes
137
+
138
+ - **Message Group ID** is required for FIFO queues
139
+ - **Deduplication ID** must be unique within a 5-minute window
140
+ - Ordering is guaranteed only within the same Message Group
141
+
142
+ ## Security Best Practices
143
+
144
+ - โœ… Use IAM roles when possible (recommended for EC2 instances)
145
+ - โœ… Store credentials in context variables rather than hardcoding
146
+ - โœ… Use environment variables for sensitive configuration
147
+ - โœ… Rotate access keys regularly
148
+ - โœ… Follow the principle of least privilege
149
+ - โœ… Enable AWS CloudTrail for audit logging
150
+
151
+ ## Error Handling
152
+
153
+ The node provides comprehensive error handling:
154
+
155
+ - **Configuration errors**: Missing or invalid AWS configuration
156
+ - **Authentication errors**: Invalid credentials or permissions
157
+ - **Queue errors**: Invalid Queue URL or insufficient permissions
158
+ - **Network errors**: Connection issues with AWS
159
+
160
+ All errors are logged and sent in the message payload for downstream processing.
161
+
162
+ ## Requirements
163
+
164
+ - Node.js >= 12.0.0
165
+ - Node-RED >= 2.0.0
166
+ - AWS account with SQS access
167
+ - Appropriate IAM permissions
168
+
169
+ ## License
170
+
171
+ MIT
172
+
173
+ ## Contributing
174
+
175
+ Contributions are welcome! Please feel free to submit a Pull Request.
176
+
177
+ ## Support
178
+
179
+ If you encounter any issues or have questions, please open an issue in the project repository.
@@ -0,0 +1,366 @@
1
+ <script type="text/javascript">
2
+ RED.nodes.registerType('aws-sqs-config', {
3
+ category: 'config',
4
+ defaults: {
5
+ name: { value: "" },
6
+ region: { value: "eu-central-1", required: true },
7
+ regionType: { value: "str" },
8
+ regionContext: { value: "" },
9
+ useIAMRole: { value: false, required: true },
10
+ deferMissingConfig: { value: true },
11
+ accessKeyId: { value: "" },
12
+ accessKeyIdType: { value: "str" },
13
+ accessKeyIdContext: { value: "" },
14
+ secretAccessKey: { value: "" },
15
+ secretAccessKeyType: { value: "str" },
16
+ secretAccessKeyContext: { value: "" }
17
+ },
18
+ credentials: {
19
+ accessKeyId: { type: "text" },
20
+ secretAccessKey: { type: "password" }
21
+ },
22
+ label: function() {
23
+ return this.name || "AWS SQS Config (" + this.region + ")";
24
+ },
25
+ icon: "font-awesome/fa-key",
26
+ color: "#b2e2b2",
27
+ oneditprepare: function() {
28
+ // Set initial IAM role checkbox state
29
+ var useIAMRole = this.useIAMRole === true || this.useIAMRole === "true" || this.useIAMRole === 1;
30
+ $("#node-config-input-useIAMRole").prop('checked', useIAMRole);
31
+ $("#node-config-input-deferMissingConfig").prop('checked', this.deferMissingConfig === true || this.deferMissingConfig === "true");
32
+
33
+ // Initialize tooltips
34
+ $('.node-config-input').tooltip({
35
+ delay: { show: 500, hide: 100 },
36
+ trigger: 'hover'
37
+ });
38
+
39
+ // Use only flow, global, str, env for typedInput
40
+ var stdTypes = ['str', 'flow', 'global', 'env'];
41
+
42
+ // Initialize typedInput for Region
43
+ $("#node-config-input-region-typed").typedInput({
44
+ default: 'str',
45
+ types: stdTypes,
46
+ typeField: "#node-config-input-regionType"
47
+ });
48
+ $("#node-config-input-region-typed").typedInput('type', this.regionType || 'str');
49
+
50
+ if (this.regionType === 'str') {
51
+ $("#node-config-input-region-typed").typedInput('value', this.region || 'eu-central-1');
52
+ } else {
53
+ $("#node-config-input-region-typed").typedInput('value', this.regionContext || '');
54
+ }
55
+
56
+ // Initialize typedInput for Access Key ID
57
+ $("#node-config-input-accessKeyId-typed").typedInput({
58
+ default: 'str',
59
+ types: stdTypes,
60
+ typeField: "#node-config-input-accessKeyIdType"
61
+ });
62
+ $("#node-config-input-accessKeyId-typed").typedInput('type', this.accessKeyIdType || 'str');
63
+
64
+ // Set value based on type for Access Key ID
65
+ if (this.accessKeyIdType === 'str') {
66
+ // For string type, use credentials if available
67
+ var credValue = this.credentials && this.credentials.accessKeyId ? this.credentials.accessKeyId : '';
68
+ $("#node-config-input-accessKeyId-typed").typedInput('value', credValue);
69
+ } else {
70
+ // For context types, use the stored context value
71
+ $("#node-config-input-accessKeyId-typed").typedInput('value', this.accessKeyIdContext || '');
72
+ }
73
+
74
+ // Secret Access Key type selection
75
+ if (this.secretAccessKeyType && this.secretAccessKeyType !== 'str') {
76
+ // Context type
77
+ $("#node-config-input-secretAccessKey-type").val(this.secretAccessKeyType);
78
+ $("#node-config-input-secretAccessKey-context").val(this.secretAccessKeyContext || '');
79
+ } else {
80
+ // String type
81
+ $("#node-config-input-secretAccessKey-type").val('str');
82
+ }
83
+
84
+ // Secret Access Key field visibility logic
85
+ var updateSecretAccessKeyFieldVisibility = function() {
86
+ var secretAccessKeyType = $("#node-config-input-secretAccessKey-type").val();
87
+
88
+ if (secretAccessKeyType === 'str') {
89
+ $("#secretAccessKey-str-row").show();
90
+ $("#secretAccessKey-context-row").hide();
91
+ } else {
92
+ $("#secretAccessKey-str-row").hide();
93
+ $("#secretAccessKey-context-row").show();
94
+ }
95
+ };
96
+
97
+ $("#node-config-input-secretAccessKey-type").on("change", updateSecretAccessKeyFieldVisibility);
98
+
99
+ // Call immediately to set initial state
100
+ updateSecretAccessKeyFieldVisibility();
101
+
102
+ // Also call after a short delay to ensure DOM is ready
103
+ setTimeout(updateSecretAccessKeyFieldVisibility, 100);
104
+
105
+ // Sync visible password field with hidden credentials field
106
+ $("#node-config-input-secretAccessKey-visible").on("input", function() {
107
+ $("#node-config-input-secretAccessKey").val($(this).val());
108
+ });
109
+
110
+ // Handle placeholder behavior for Secret Access Key
111
+ $("#node-config-input-secretAccessKey-visible").on("focus", function() {
112
+ if ($(this).attr('placeholder') === 'โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข') {
113
+ $(this).attr('placeholder', 'Enter new secret key or leave empty to keep existing');
114
+ }
115
+ });
116
+
117
+ $("#node-config-input-secretAccessKey-visible").on("blur", function() {
118
+ var inputVal = $(this).val();
119
+ if (!inputVal && this.credentials && this.credentials.has_secretAccessKey) {
120
+ $(this).attr('placeholder', 'โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข');
121
+ }
122
+ }.bind(this));
123
+
124
+ // Load existing secret access key if available
125
+ if (this.credentials && this.credentials.secretAccessKey) {
126
+ $("#node-config-input-secretAccessKey-visible").val(this.credentials.secretAccessKey);
127
+ $("#node-config-input-secretAccessKey").val(this.credentials.secretAccessKey);
128
+ } else if (this.credentials && this.credentials.has_secretAccessKey) {
129
+ // Secret key exists but not shown for security - show placeholder
130
+ $("#node-config-input-secretAccessKey-visible").attr('placeholder', 'โ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข');
131
+ $("#node-config-input-secretAccessKey-visible").val('');
132
+ } else {
133
+ // No secret key - set default placeholder
134
+ $("#node-config-input-secretAccessKey-visible").attr('placeholder', 'Enter secret access key');
135
+ $("#node-config-input-secretAccessKey-visible").val('');
136
+ }
137
+
138
+ // Handle IAM role toggle
139
+ $('#node-config-input-useIAMRole').change(function() {
140
+ var isIAMRole = $(this).is(':checked');
141
+
142
+ if (isIAMRole) {
143
+ $('.credentials-row').hide();
144
+ } else {
145
+ $('.credentials-row').show();
146
+ // Re-apply field visibility logic
147
+ updateSecretAccessKeyFieldVisibility();
148
+ }
149
+ });
150
+
151
+ // Set initial visibility
152
+ if (useIAMRole) {
153
+ $('.credentials-row').hide();
154
+ } else {
155
+ // Ensure proper field visibility
156
+ setTimeout(function() {
157
+ updateSecretAccessKeyFieldVisibility();
158
+ }, 200);
159
+ }
160
+ },
161
+ oneditsave: function() {
162
+ // Save basic configuration
163
+ this.name = $("#node-config-input-name").val();
164
+ // Handle Region
165
+ var regionType = $("#node-config-input-regionType").val();
166
+ var regionValue = $("#node-config-input-region-typed").typedInput('value');
167
+ this.regionType = regionType;
168
+ if (regionType === 'str') {
169
+ this.region = regionValue || '';
170
+ this.regionContext = '';
171
+ $("#node-config-input-region-context").val('');
172
+ $("#node-config-input-region").val(regionValue || '');
173
+ } else {
174
+ this.region = '';
175
+ this.regionContext = regionValue || '';
176
+ $("#node-config-input-region").val('');
177
+ $("#node-config-input-region-context").val(regionValue || '');
178
+ }
179
+ this.useIAMRole = $("#node-config-input-useIAMRole").is(':checked');
180
+ this.deferMissingConfig = $("#node-config-input-deferMissingConfig").is(':checked');
181
+
182
+ // Handle Access Key ID (TypedInput logic)
183
+ var accessKeyIdType = $("#node-config-input-accessKeyIdType").val();
184
+ var accessKeyIdValue = $("#node-config-input-accessKeyId-typed").typedInput('value');
185
+
186
+ // Explicitly set the type first
187
+ this.accessKeyIdType = accessKeyIdType;
188
+
189
+ if (accessKeyIdType === 'str') {
190
+ // String type - save to credentials, clear from defaults
191
+ this.accessKeyId = '';
192
+ this.accessKeyIdContext = '';
193
+ $("#node-config-input-accessKeyId-context").val('');
194
+ // Set the credential field
195
+ $("#node-config-input-accessKeyId").val(accessKeyIdValue || '');
196
+ } else {
197
+ // Context type - save to context field, clear credentials
198
+ this.accessKeyId = '';
199
+ this.accessKeyIdContext = accessKeyIdValue || '';
200
+ $("#node-config-input-accessKeyId").val('');
201
+ $("#node-config-input-accessKeyId-context").val(accessKeyIdValue || '');
202
+ }
203
+
204
+ // Handle Secret Access Key based on type
205
+ var secretAccessKeyType = $("#node-config-input-secretAccessKey-type").val();
206
+ this.secretAccessKeyType = secretAccessKeyType;
207
+
208
+ if (secretAccessKeyType === 'str') {
209
+ // String secret key - save to credentials, clear context
210
+ this.secretAccessKeyContext = '';
211
+ this.secretAccessKey = ''; // Clear from defaults
212
+ $("#node-config-input-secretAccessKeyContext").val('');
213
+ // Sync visible field to hidden credentials field only if not empty
214
+ var visibleSecretAccessKey = $("#node-config-input-secretAccessKey-visible").val();
215
+ if (visibleSecretAccessKey) {
216
+ $("#node-config-input-secretAccessKey").val(visibleSecretAccessKey);
217
+ }
218
+ // If empty, keep existing secret key (don't overwrite)
219
+ } else {
220
+ // Context secret key - save context reference
221
+ var secretAccessKeyValue = $("#node-config-input-secretAccessKey-context").val();
222
+ this.secretAccessKeyContext = secretAccessKeyValue || '';
223
+ this.secretAccessKey = ''; // Clear from defaults
224
+ $("#node-config-input-secretAccessKeyContext").val(secretAccessKeyValue || '');
225
+ }
226
+ }
227
+ });
228
+ </script>
229
+
230
+ <script type="text/x-red" data-template-name="aws-sqs-config">
231
+ <div class="form-row">
232
+ <label for="node-config-input-name">
233
+ <i class="fa fa-tag"></i> Name
234
+ </label>
235
+ <input type="text" id="node-config-input-name" placeholder="Configuration Name" style="width: 70%;">
236
+ </div>
237
+ <div class="form-row">
238
+ <label for="node-config-input-region-typed">
239
+ <i class="fa fa-globe"></i> Region
240
+ </label>
241
+ <input type="text" id="node-config-input-region-typed" style="width: 70%;" placeholder="eu-central-1">
242
+ <input type="hidden" id="node-config-input-regionType">
243
+ </div>
244
+ <div class="form-row">
245
+ <label for="node-config-input-useIAMRole">
246
+ <i class="fa fa-user"></i> Use IAM Role
247
+ </label>
248
+ <input type="checkbox" id="node-config-input-useIAMRole" style="display: inline-block; width: auto; vertical-align: middle;">
249
+ <span style="margin-left: 5px; vertical-align: middle;">Use IAM role for authentication<br><small>(recommended for EC2 instances)</small></span>
250
+ </div>
251
+ <div class="form-row">
252
+ <label for="node-config-input-deferMissingConfig">
253
+ <i class="fa fa-clock-o"></i> Defer Missing Config
254
+ </label>
255
+ <input type="checkbox" id="node-config-input-deferMissingConfig" style="display: inline-block; width: auto; vertical-align: middle;">
256
+ <span style="margin-left: 5px; vertical-align: middle;">Wait for credentials/region without throwing errors</span>
257
+ </div>
258
+ <div class="form-row credentials-row">
259
+ <label for="node-config-input-accessKeyId-typed">
260
+ <i class="fa fa-key"></i> Access Key ID
261
+ </label>
262
+ <input type="text" id="node-config-input-accessKeyId-typed" style="width: 70%;">
263
+ <input type="hidden" id="node-config-input-accessKeyIdType">
264
+ </div>
265
+ <div class="form-row credentials-row">
266
+ <label for="node-config-input-secretAccessKey-type">
267
+ <i class="fa fa-key"></i> Secret Access Key Type
268
+ </label>
269
+ <select id="node-config-input-secretAccessKey-type" style="width: 70%;">
270
+ <option value="str">Direct Secret Key (secure)</option>
271
+ <option value="flow">Flow Context</option>
272
+ <option value="global">Global Context</option>
273
+ <option value="env">Environment Variable</option>
274
+ </select>
275
+ </div>
276
+ <div class="form-row credentials-row" id="secretAccessKey-str-row">
277
+ <label for="node-config-input-secretAccessKey-visible">
278
+ <i class="fa fa-key"></i> Secret Access Key
279
+ </label>
280
+ <input type="password" id="node-config-input-secretAccessKey-visible" style="width: 70%;">
281
+ </div>
282
+ <div class="form-row credentials-row" id="secretAccessKey-context-row" style="display: none;">
283
+ <label for="node-config-input-secretAccessKey-context">
284
+ <i class="fa fa-code"></i> Variable Name
285
+ </label>
286
+ <input type="text" id="node-config-input-secretAccessKey-context" style="width: 70%;">
287
+ </div>
288
+ <!-- Hidden fields for Node-RED credentials system -->
289
+ <input type="text" id="node-config-input-accessKeyId" style="display: none;">
290
+ <input type="password" id="node-config-input-secretAccessKey" style="display: none;">
291
+ <!-- Hidden field for region -->
292
+ <input type="text" id="node-config-input-region" style="display: none;">
293
+ <!-- Hidden field for region context value -->
294
+ <input type="text" id="node-config-input-region-context" style="display: none;">
295
+ <!-- Hidden field for accessKeyId context value -->
296
+ <input type="text" id="node-config-input-accessKeyId-context" style="display: none;">
297
+ <!-- Hidden field for secretAccessKeyContext -->
298
+ <input type="text" id="node-config-input-secretAccessKeyContext" style="display: none;">
299
+ </script>
300
+
301
+ <script type="text/x-red" data-help-name="aws-sqs-config">
302
+ <p>AWS SQS configuration node with flexible context support:</p>
303
+
304
+ <h3>Authentication Methods</h3>
305
+ <ul>
306
+ <li><b>IAM Role</b>: Use IAM role for authentication (recommended for EC2 instances)</li>
307
+ <li><b>Access Keys</b>: Use AWS access key and secret key</li>
308
+ </ul>
309
+
310
+ <h3>Credential Sources</h3>
311
+ <p>When not using IAM role, credentials support multiple input types:</p>
312
+ <ul>
313
+ <li><b>String</b>: Stored securely in Node-RED credentials (encrypted)</li>
314
+ <li><b>Flow Context</b>: Retrieved from flow context variables</li>
315
+ <li><b>Global Context</b>: Retrieved from global context variables</li>
316
+ <li><b>Environment Variable</b>: Retrieved from environment variables</li>
317
+ </ul>
318
+
319
+ <h3>Security Notes</h3>
320
+ <ul>
321
+ <li>String credentials are stored encrypted in Node-RED's credentials store</li>
322
+ <li>Context types only store variable names, actual credentials retrieved at runtime</li>
323
+ <li>Use IAM roles when possible for better security</li>
324
+ <li>Rotate access keys regularly</li>
325
+ <li>Follow the principle of least privilege</li>
326
+ </ul>
327
+ </script>
328
+
329
+ <style>
330
+ .form-row {
331
+ margin-bottom: 10px;
332
+ }
333
+ .form-row label {
334
+ display: inline-block;
335
+ width: 120px;
336
+ vertical-align: top;
337
+ margin-top: 6px;
338
+ }
339
+ .form-row input[type="text"],
340
+ .form-row input[type="password"],
341
+ .form-row select {
342
+ width: 70%;
343
+ }
344
+ .form-row input[type="checkbox"] {
345
+ width: auto;
346
+ margin: 0;
347
+ vertical-align: middle;
348
+ }
349
+ .credentials-row {
350
+ margin-top: 10px;
351
+ }
352
+ .help-text {
353
+ font-size: 0.8em;
354
+ color: #666;
355
+ margin-top: 4px;
356
+ margin-left: 125px;
357
+ }
358
+ .error-text {
359
+ color: #d00;
360
+ font-size: 0.8em;
361
+ margin-top: 4px;
362
+ }
363
+ .input-error {
364
+ border-color: #d00 !important;
365
+ }
366
+ </style>