@inteli.city/node-red-contrib-http-plus 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/LICENSE +202 -0
- package/README.md +516 -0
- package/http-auth-config+.html +167 -0
- package/http-auth-config+.js +29 -0
- package/httpin+.html +494 -0
- package/httpin+.js +626 -0
- package/httpproxy+.html +140 -0
- package/httpproxy+.js +138 -0
- package/httprequest+.html +233 -0
- package/httprequest+.js +311 -0
- package/libs/swagger.js +213 -0
- package/package.json +37 -0
package/README.md
ADDED
|
@@ -0,0 +1,516 @@
|
|
|
1
|
+
# @inteli.city/node-red-contrib-http+
|
|
2
|
+
|
|
3
|
+
Enhanced HTTP nodes for Node-RED with built-in authentication and request validation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## What is this?
|
|
8
|
+
|
|
9
|
+
`@inteli.city/node-red-contrib-http+` provides enhanced versions of Node-RED's HTTP nodes with built-in authentication, validation, and improved request handling.
|
|
10
|
+
|
|
11
|
+
These nodes are designed to be compatible with the standard Node-RED HTTP flow while adding capabilities commonly implemented manually in flows.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Key differences from standard HTTP nodes
|
|
16
|
+
|
|
17
|
+
| Feature | Standard nodes | http+ nodes |
|
|
18
|
+
|---------|----------------|-------------|
|
|
19
|
+
| Authentication | Not built-in | Built-in (Basic + Cognito) |
|
|
20
|
+
| Request validation | Manual (function nodes) | Built-in (Zod) |
|
|
21
|
+
| File upload handling | Basic | Structured (`msg.files`) |
|
|
22
|
+
| Data normalization | Manual | Built-in via Zod transforms |
|
|
23
|
+
| Streaming responses | Limited/manual | First-class support |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Compatibility
|
|
28
|
+
|
|
29
|
+
- `http.in+` and `http.out+` follow the same flow model as the standard nodes
|
|
30
|
+
- `http.request+` is a drop-in replacement for the standard `http request` node
|
|
31
|
+
- Existing flows can be migrated incrementally
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
Standard: http in ──→ function ──→ http response
|
|
35
|
+
With http+: http.in+ ──→ (validated + authenticated) ──→ http.out+
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## When to use http+ nodes
|
|
41
|
+
|
|
42
|
+
Use http+ when you need:
|
|
43
|
+
|
|
44
|
+
- Authentication at the HTTP boundary
|
|
45
|
+
- Input validation without extra function nodes
|
|
46
|
+
- Cleaner and more predictable request handling
|
|
47
|
+
|
|
48
|
+
Use standard nodes when:
|
|
49
|
+
|
|
50
|
+
- You need minimal setup
|
|
51
|
+
- You are prototyping quickly without constraints
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Index
|
|
56
|
+
|
|
57
|
+
- [Nodes](#nodes)
|
|
58
|
+
- [Install](#install)
|
|
59
|
+
- [Core Concepts](#core-concepts)
|
|
60
|
+
- [Authentication](#authentication)
|
|
61
|
+
- [Validation with Zod](#validation-with-zod)
|
|
62
|
+
- [Swagger / OpenAPI](#swagger--openapi)
|
|
63
|
+
- [File uploads](#file-uploads)
|
|
64
|
+
- [http.request+](#httprequest)
|
|
65
|
+
- [http.out+](#httpout)
|
|
66
|
+
- [Best practices](#best-practices)
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Nodes
|
|
71
|
+
|
|
72
|
+
| Node | Role |
|
|
73
|
+
|------|------|
|
|
74
|
+
| `http.in+` | Receives HTTP requests. Runs auth and Zod validation before sending `msg` downstream. |
|
|
75
|
+
| `http.request+` | Makes outgoing HTTP requests. Drop-in replacement for the standard `http request` node. |
|
|
76
|
+
| `http.out+` | Sends the HTTP response back to the caller. Works identically to the standard `http response` node. |
|
|
77
|
+
|
|
78
|
+
## Install
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
cd ~/.node-red
|
|
82
|
+
npm install @inteli.city/node-red-contrib-http+
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Core Concepts
|
|
88
|
+
|
|
89
|
+
### Message properties
|
|
90
|
+
|
|
91
|
+
| Property | Contains |
|
|
92
|
+
|----------|----------|
|
|
93
|
+
| `msg.payload` | Request body (POST/PUT/PATCH) or query object (GET) |
|
|
94
|
+
| `msg.req.query` | Query string as object — `GET /search?term=abc` → `{ term: "abc" }` |
|
|
95
|
+
| `msg.req.params` | Route parameters — `GET /users/42` → `{ id: "42" }` |
|
|
96
|
+
| `msg.req.headers` | Request headers |
|
|
97
|
+
| `msg.validated` | Parsed + validated object from Zod (only when validation passes) |
|
|
98
|
+
| `msg.user` | Mapped user identity. For Basic Auth: the username string. For Cognito: fields mapped from the JWT payload (only when "Expose user to flow" is enabled). |
|
|
99
|
+
| `msg.res` | Response handle — passed to `http.out+` to send the reply |
|
|
100
|
+
|
|
101
|
+
### Basic flow
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
http.in+ ──→ [your logic] ──→ http.out+
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
inject ──→ http.request+ ──→ debug
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Authentication
|
|
114
|
+
|
|
115
|
+
Configure authentication by creating an **`http.auth.config+`** config node and attaching it to `http.in+`.
|
|
116
|
+
|
|
117
|
+
| Type | Behaviour |
|
|
118
|
+
|------|-----------|
|
|
119
|
+
| None | All requests pass through |
|
|
120
|
+
| Basic Auth | Validates `Authorization: Basic …` header. Browser login popup triggered automatically via `WWW-Authenticate`. Supports single-user and multi-user modes (see below). |
|
|
121
|
+
| Cognito JWT | Validates a Bearer token against a JWKS endpoint. Token accepted from `Authorization: Bearer <token>` or `?token=`. JWKS keys are cached for 1 hour. |
|
|
122
|
+
|
|
123
|
+
Failed authentication returns **401** and stops the flow.
|
|
124
|
+
|
|
125
|
+
### Basic Auth modes
|
|
126
|
+
|
|
127
|
+
**Single-user (default):** Enter a username and password directly in the config node. Credentials are stored securely via Node-RED's credentials system (not in the exported flow).
|
|
128
|
+
|
|
129
|
+
**Multiple users (JSON):** Enable the "Use multiple users" option and provide a JSON object mapping usernames to passwords:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"admin": "password123",
|
|
134
|
+
"user": "abc123"
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
> **Warning:** Passwords in JSON mode are stored in plain text in the flow configuration. Use only in trusted environments.
|
|
139
|
+
|
|
140
|
+
### JWKS caching
|
|
141
|
+
|
|
142
|
+
Cognito public keys (JWKS) are cached in memory to reduce latency and avoid repeated requests to AWS.
|
|
143
|
+
|
|
144
|
+
- Cache duration: **1 hour**
|
|
145
|
+
- Keys are cached per `kid` (key ID)
|
|
146
|
+
- A new key is fetched automatically if an unknown `kid` is received
|
|
147
|
+
|
|
148
|
+
This improves performance while still supporting key rotation. If AWS rotates signing keys, new keys will be picked up automatically after cache expiration or when a new `kid` appears.
|
|
149
|
+
|
|
150
|
+
### Cognito user mapping
|
|
151
|
+
|
|
152
|
+
After successful JWT validation, the `Authorization` header and `?token=` query parameter are always removed from the request before it enters the flow.
|
|
153
|
+
|
|
154
|
+
To expose user identity in `msg.user`, enable **"Expose user to flow"** in the config node and provide a JSON mapping:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"id": "sub",
|
|
159
|
+
"email": "email",
|
|
160
|
+
"roles": "cognito:groups"
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
Keys are the output field names in `msg.user`; values are the JWT claim names to read from. Fields missing from the token are set to `undefined`.
|
|
165
|
+
|
|
166
|
+
If **"Expose user to flow"** is disabled, `msg.user` is not set — no JWT data propagates into the flow.
|
|
167
|
+
|
|
168
|
+
### Request sanitization
|
|
169
|
+
|
|
170
|
+
After successful authentication, credentials are removed from the request before dispatch:
|
|
171
|
+
|
|
172
|
+
| Auth type | Removed |
|
|
173
|
+
|-----------|---------|
|
|
174
|
+
| Basic Auth | `msg.req.headers.authorization` |
|
|
175
|
+
| Cognito JWT | `msg.req.headers.authorization`, `msg.req.query.token` |
|
|
176
|
+
|
|
177
|
+
This prevents tokens and passwords from leaking into downstream nodes.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Validation with Zod
|
|
182
|
+
|
|
183
|
+
`http.in+` can validate every incoming request before passing it downstream.
|
|
184
|
+
|
|
185
|
+
### Enabling validation
|
|
186
|
+
|
|
187
|
+
1. Open the `http.in+` node editor.
|
|
188
|
+
2. Check **Enable Zod validation**.
|
|
189
|
+
3. Enter a Zod schema expression in the **Schema** textarea.
|
|
190
|
+
|
|
191
|
+
The schema receives a single object:
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
{
|
|
195
|
+
body: // request body (msg.payload)
|
|
196
|
+
query: // query string parameters (msg.req.query)
|
|
197
|
+
params: // route parameters (msg.req.params)
|
|
198
|
+
files: // uploaded file metadata (when file upload is enabled)
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Use the appropriate field depending on the HTTP method:
|
|
203
|
+
|
|
204
|
+
| Method | Use |
|
|
205
|
+
|--------|-----|
|
|
206
|
+
| GET, DELETE | `query`, `params` only — no body |
|
|
207
|
+
| POST, PUT, PATCH | `body`, `query`, `params` |
|
|
208
|
+
|
|
209
|
+
> Defining `body` in a GET or DELETE schema is ignored at runtime. A warning is logged at deploy time.
|
|
210
|
+
|
|
211
|
+
> Full usage guidance (method table, type coercion, Swagger behavior) is available inline in the node editor UI.
|
|
212
|
+
|
|
213
|
+
If validation **passes**, `msg.validated` contains the parsed result and the flow continues normally.
|
|
214
|
+
|
|
215
|
+
If validation **fails**, the node returns HTTP **400** immediately — no downstream node is executed:
|
|
216
|
+
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"error": "invalid_request",
|
|
220
|
+
"details": [
|
|
221
|
+
{ "code": "invalid_type", "path": ["body", "age"], "message": "Expected number, received string" }
|
|
222
|
+
]
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Important: query and param values are always strings
|
|
227
|
+
|
|
228
|
+
HTTP query strings and route params arrive as strings. Use `z.coerce` to convert them:
|
|
229
|
+
|
|
230
|
+
```js
|
|
231
|
+
z.coerce.number() // "42" → 42
|
|
232
|
+
z.coerce.boolean() // "true" → true
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Transforms and normalization
|
|
236
|
+
|
|
237
|
+
Zod can transform values as part of validation — trimming whitespace, converting case, coercing types, etc. The transformed result is what ends up in `msg.validated`. `msg.payload` always remains the original, unmodified request body.
|
|
238
|
+
|
|
239
|
+
```js
|
|
240
|
+
z.object({
|
|
241
|
+
query: z.object({
|
|
242
|
+
term: z.string().trim().toLowerCase(),
|
|
243
|
+
page: z.coerce.number().int().min(1)
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
The flow receives `msg.validated.query.term` already trimmed and lowercased — no manual cleanup needed.
|
|
249
|
+
|
|
250
|
+
### File validation
|
|
251
|
+
|
|
252
|
+
Zod validates file **metadata** (mime type, size, etc.), not file content. Files from the upload are available in `msg.files` as an array — validate the first entry or a named field:
|
|
253
|
+
|
|
254
|
+
```js
|
|
255
|
+
z.object({
|
|
256
|
+
files: z.object({
|
|
257
|
+
avatar: z.object({
|
|
258
|
+
mimetype: z.enum(["image/png", "image/jpeg"]),
|
|
259
|
+
size: z.number().max(2_000_000)
|
|
260
|
+
})
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Examples
|
|
268
|
+
|
|
269
|
+
### Example 1 — Body validation (POST)
|
|
270
|
+
|
|
271
|
+
**Route:** `POST /users`
|
|
272
|
+
|
|
273
|
+
**Schema:**
|
|
274
|
+
```js
|
|
275
|
+
z.object({
|
|
276
|
+
body: z.object({
|
|
277
|
+
name: z.string().min(1),
|
|
278
|
+
age: z.number().int().min(0)
|
|
279
|
+
})
|
|
280
|
+
})
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
**Valid request body:**
|
|
284
|
+
```json
|
|
285
|
+
{ "name": "Alice", "age": 30 }
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
**Invalid — returns 400:**
|
|
289
|
+
```json
|
|
290
|
+
{ "name": "", "age": -1 }
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
### Example 2 — Query validation (GET)
|
|
296
|
+
|
|
297
|
+
**Route:** `GET /search?term=node-red&page=2`
|
|
298
|
+
|
|
299
|
+
**Schema:**
|
|
300
|
+
```js
|
|
301
|
+
z.object({
|
|
302
|
+
query: z.object({
|
|
303
|
+
term: z.string().min(1),
|
|
304
|
+
page: z.coerce.number().int().min(1).optional()
|
|
305
|
+
})
|
|
306
|
+
})
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`page` arrives as a string from the URL — `z.coerce.number()` converts it automatically.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### Example 3 — Route parameter validation
|
|
314
|
+
|
|
315
|
+
**Route:** `GET /users/:id`
|
|
316
|
+
|
|
317
|
+
**Schema:**
|
|
318
|
+
```js
|
|
319
|
+
z.object({
|
|
320
|
+
params: z.object({
|
|
321
|
+
id: z.string().uuid()
|
|
322
|
+
})
|
|
323
|
+
})
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
Rejects any request where `:id` is not a valid UUID before your logic runs.
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
### Example 4 — Combined: params + query + body
|
|
331
|
+
|
|
332
|
+
**Route:** `PATCH /users/:id?notify=true`
|
|
333
|
+
|
|
334
|
+
**Schema:**
|
|
335
|
+
```js
|
|
336
|
+
z.object({
|
|
337
|
+
params: z.object({
|
|
338
|
+
id: z.string().uuid()
|
|
339
|
+
}),
|
|
340
|
+
query: z.object({
|
|
341
|
+
notify: z.coerce.boolean().optional()
|
|
342
|
+
}).optional(),
|
|
343
|
+
body: z.object({
|
|
344
|
+
email: z.string().email().optional(),
|
|
345
|
+
role: z.enum(["admin", "user", "viewer"]).optional()
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
`msg.validated` will contain the fully parsed and typed object for use downstream.
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Validation error format
|
|
355
|
+
|
|
356
|
+
```json
|
|
357
|
+
{
|
|
358
|
+
"error": "invalid_request",
|
|
359
|
+
"details": [
|
|
360
|
+
{
|
|
361
|
+
"code": "invalid_type",
|
|
362
|
+
"path": ["body", "age"],
|
|
363
|
+
"message": "Expected number, received string"
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
"code": "too_small",
|
|
367
|
+
"path": ["body", "name"],
|
|
368
|
+
"message": "String must contain at least 1 character(s)"
|
|
369
|
+
}
|
|
370
|
+
]
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
`details` is the raw Zod `ZodError.errors` array. Each entry contains a `path` array indicating exactly which field failed.
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## Best practices
|
|
379
|
+
|
|
380
|
+
| Scenario | Recommendation |
|
|
381
|
+
|----------|----------------|
|
|
382
|
+
| Required identifiers | Use route params (`:id`) with `z.string().uuid()` or similar |
|
|
383
|
+
| Filters and options | Use query string with `z.coerce` for number/boolean |
|
|
384
|
+
| Structured input data | Use request body (`body`) — POST/PUT/PATCH only |
|
|
385
|
+
| GET / DELETE filtering | Use `query` — never `body` |
|
|
386
|
+
| All query/param values | Always use `z.coerce.*` — they arrive as strings |
|
|
387
|
+
| Optional sections | Wrap entire `query` or `params` in `.optional()` if the route doesn't always have them |
|
|
388
|
+
| Schema errors at deploy | The node logs the error and disables validation — it does **not** crash |
|
|
389
|
+
| Swagger visibility | Only nodes with a valid Zod schema appear in `/docs` |
|
|
390
|
+
|
|
391
|
+
---
|
|
392
|
+
|
|
393
|
+
## Swagger / OpenAPI
|
|
394
|
+
|
|
395
|
+
`http.in+` nodes with a valid Zod schema are automatically registered in an OpenAPI 3.0 spec. No extra configuration is required.
|
|
396
|
+
|
|
397
|
+
| Endpoint | Returns |
|
|
398
|
+
|----------|---------|
|
|
399
|
+
| `GET /openapi.json` | Raw OpenAPI spec (JSON) |
|
|
400
|
+
| `GET /docs` | Swagger UI |
|
|
401
|
+
|
|
402
|
+
Only endpoints where Zod validation is enabled **and** the schema compiled successfully appear in the spec. The spec updates automatically on each deploy — no stale entries.
|
|
403
|
+
|
|
404
|
+
For schema field usage, method rules, and type coercion guidance, see the inline help panel in the node editor.
|
|
405
|
+
|
|
406
|
+
---
|
|
407
|
+
|
|
408
|
+
## File uploads
|
|
409
|
+
|
|
410
|
+
`http.in+` supports multipart (`multipart/form-data`) file uploads on **POST** routes. The upload option is not available for GET, PUT, PATCH, or DELETE.
|
|
411
|
+
|
|
412
|
+
If file upload is enabled, uploaded files are available in `msg.files` — they are **not** included in `msg.payload`.
|
|
413
|
+
|
|
414
|
+
### Enabling uploads
|
|
415
|
+
|
|
416
|
+
1. Open the `http.in+` node editor.
|
|
417
|
+
2. Check **Enable file uploads**.
|
|
418
|
+
3. Choose **Storage**: `Memory (buffer)` or `Disk (temp files)`.
|
|
419
|
+
4. Set **Max size (MB)** (default: 5 MB).
|
|
420
|
+
5. For disk storage, optionally set a **Temp dir** (defaults to the OS temp directory).
|
|
421
|
+
|
|
422
|
+
### `msg.files`
|
|
423
|
+
|
|
424
|
+
When files are uploaded, `msg.files` is an array of objects:
|
|
425
|
+
|
|
426
|
+
| Property | Present | Contains |
|
|
427
|
+
|----------|---------|----------|
|
|
428
|
+
| `fieldname` | always | Form field name used for the upload |
|
|
429
|
+
| `originalname` | always | Original filename from the client |
|
|
430
|
+
| `mimetype` | always | MIME type (e.g. `image/png`) |
|
|
431
|
+
| `size` | always | File size in bytes |
|
|
432
|
+
| `storage` | always | `"memory"` or `"disk"` |
|
|
433
|
+
| `buffer` | memory only | `Buffer` containing the file data |
|
|
434
|
+
| `path` | disk only | Absolute path to the saved file |
|
|
435
|
+
|
|
436
|
+
### Size limit
|
|
437
|
+
|
|
438
|
+
If a file exceeds the configured limit, the node returns **413** immediately:
|
|
439
|
+
|
|
440
|
+
```json
|
|
441
|
+
{ "error": "file_too_large" }
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
### Disk storage — cleanup
|
|
445
|
+
|
|
446
|
+
When using disk storage, files are written to the temp directory and **not deleted automatically**. Your flow is responsible for removing them after use (e.g. via a function node calling `fs.unlink`).
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## http.request+
|
|
451
|
+
|
|
452
|
+
Drop-in replacement for the standard `http request` node with identical behaviour.
|
|
453
|
+
|
|
454
|
+
**Input message properties used:**
|
|
455
|
+
|
|
456
|
+
| Property | Effect |
|
|
457
|
+
|----------|--------|
|
|
458
|
+
| `msg.url` | Target URL (overrides node config) |
|
|
459
|
+
| `msg.method` | HTTP method (when node is set to "use msg.method") |
|
|
460
|
+
| `msg.payload` | Request body |
|
|
461
|
+
| `msg.headers` | Additional request headers |
|
|
462
|
+
|
|
463
|
+
**Output message properties set:**
|
|
464
|
+
|
|
465
|
+
| Property | Contains |
|
|
466
|
+
|----------|----------|
|
|
467
|
+
| `msg.payload` | Response body |
|
|
468
|
+
| `msg.statusCode` | HTTP status code |
|
|
469
|
+
| `msg.headers` | Response headers |
|
|
470
|
+
| `msg.responseUrl` | Final URL after redirects |
|
|
471
|
+
| `msg.redirectList` | List of redirects followed |
|
|
472
|
+
|
|
473
|
+
Supports TLS config, proxy config (`http.proxy+`), Basic/Digest/Bearer auth, and persistent connections.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## http.out+
|
|
478
|
+
|
|
479
|
+
Sends the HTTP response back to the original caller. Must be connected to the same flow started by `http.in+`.
|
|
480
|
+
|
|
481
|
+
**Controlling the response via `msg`:**
|
|
482
|
+
|
|
483
|
+
| Property | Effect |
|
|
484
|
+
|----------|--------|
|
|
485
|
+
| `msg.payload` | Response body |
|
|
486
|
+
| `msg.statusCode` | HTTP status code (default: 200) |
|
|
487
|
+
| `msg.headers` | Extra response headers |
|
|
488
|
+
| `msg.cookies` | Cookies to set or clear |
|
|
489
|
+
|
|
490
|
+
If `msg.payload` is an object, the response is sent as JSON automatically.
|
|
491
|
+
|
|
492
|
+
### Streaming responses
|
|
493
|
+
|
|
494
|
+
You can stream a response instead of sending a buffer. Useful for large files or when proxying a stream from another source.
|
|
495
|
+
|
|
496
|
+
Set `msg.stream` to any readable Node.js stream:
|
|
497
|
+
|
|
498
|
+
```js
|
|
499
|
+
msg.stream = fs.createReadStream("/tmp/file.zip");
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
If `msg.stream` is set, the stream is piped directly to the response. If it is not set, `msg.payload` is sent normally.
|
|
503
|
+
|
|
504
|
+
`msg.headers` and `msg.statusCode` behave the same in both modes.
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## File structure
|
|
509
|
+
|
|
510
|
+
```
|
|
511
|
+
httpproxy+.js/.html — http.proxy+ config node (proxy for http.request+); includes proxy resolution utility
|
|
512
|
+
httpin+.js/.html — http.in+ and http.out+ nodes
|
|
513
|
+
httprequest+.js/.html — http.request+ node
|
|
514
|
+
http-auth-config+.js/.html — http.auth.config+ config node
|
|
515
|
+
libs/swagger.js — OpenAPI spec generation and /docs + /openapi.json route registration
|
|
516
|
+
```
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Authentication config node for http.in+
|
|
3
|
+
Supports: none, Basic Auth (simple + multi-user), AWS Cognito JWT (JWKS)
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
<style>
|
|
7
|
+
#auth-help-simple code,
|
|
8
|
+
#auth-help-json code,
|
|
9
|
+
.auth-row-cognito.help-text code {
|
|
10
|
+
font-size: 12px;
|
|
11
|
+
background: none;
|
|
12
|
+
padding: 0;
|
|
13
|
+
}
|
|
14
|
+
</style>
|
|
15
|
+
|
|
16
|
+
<script type="text/html" data-template-name="http.auth.config+">
|
|
17
|
+
<div style="min-width:580px; height:0; overflow:hidden;"></div>
|
|
18
|
+
<div class="form-row">
|
|
19
|
+
<label for="node-config-input-name"><i class="fa fa-tag"></i> Name</label>
|
|
20
|
+
<input type="text" id="node-config-input-name" placeholder="optional label">
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="form-row">
|
|
24
|
+
<label for="node-config-input-authType"><i class="fa fa-lock"></i> Auth Type</label>
|
|
25
|
+
<select id="node-config-input-authType" style="width:70%;">
|
|
26
|
+
<option value="none">None</option>
|
|
27
|
+
<option value="basic">Basic Auth</option>
|
|
28
|
+
<option value="cognito">AWS Cognito JWT</option>
|
|
29
|
+
</select>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- Basic Auth -->
|
|
33
|
+
<div class="form-row auth-row-basic" style="display:none;">
|
|
34
|
+
<input type="checkbox" id="node-config-input-useJsonUsers" style="display:inline-block; width:auto; vertical-align:top;">
|
|
35
|
+
<label for="node-config-input-useJsonUsers" style="width:auto;"> Use multiple users (JSON)</label>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="auth-row-basic" id="auth-help-simple" style="display:none; font-size:12px; color:#666; margin: -4px 0 8px 110px; line-height:1.5;">
|
|
39
|
+
Credentials are stored securely via Node-RED's credentials system.
|
|
40
|
+
</div>
|
|
41
|
+
<div class="auth-row-basic" id="auth-help-json" style="display:none; font-size:12px; color:#666; margin: -4px 0 8px 110px; line-height:1.5;">
|
|
42
|
+
Passwords are stored in plain text in the flow configuration.<br>
|
|
43
|
+
<em>Note: use only in trusted environments.</em>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<div class="form-row auth-row-basic auth-simple-row" style="display:none;">
|
|
47
|
+
<label for="node-config-input-username"><i class="fa fa-user"></i> Username</label>
|
|
48
|
+
<input type="text" id="node-config-input-username" autocomplete="off">
|
|
49
|
+
</div>
|
|
50
|
+
<div class="form-row auth-row-basic auth-simple-row" style="display:none;">
|
|
51
|
+
<label for="node-config-input-password"><i class="fa fa-key"></i> Password</label>
|
|
52
|
+
<input type="password" id="node-config-input-password" autocomplete="off">
|
|
53
|
+
</div>
|
|
54
|
+
|
|
55
|
+
<div class="form-row auth-row-basic auth-json-row" style="display:none;">
|
|
56
|
+
<label for="node-config-input-usersJson" style="vertical-align:top; margin-top:4px;"><i class="fa fa-users"></i> Users</label>
|
|
57
|
+
<textarea id="node-config-input-usersJson" rows="5"
|
|
58
|
+
style="width:70%; font-family:monospace; font-size:12px; resize:vertical;"
|
|
59
|
+
placeholder='{ "admin": "password123", "user1": "abc123" }'
|
|
60
|
+
autocomplete="off"></textarea>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- Cognito JWT -->
|
|
64
|
+
<div class="form-row auth-row-cognito" style="display:none;">
|
|
65
|
+
<label for="node-config-input-jwksUrl"><i class="fa fa-link"></i> JWKS URL</label>
|
|
66
|
+
<input type="text" id="node-config-input-jwksUrl"
|
|
67
|
+
placeholder="https://cognito-idp.{region}.amazonaws.com/{pool-id}/.well-known/jwks.json"
|
|
68
|
+
style="width:70%;">
|
|
69
|
+
</div>
|
|
70
|
+
<div class="form-row auth-row-cognito" style="display:none;">
|
|
71
|
+
<label for="node-config-input-audience"><i class="fa fa-id-badge"></i> Audience</label>
|
|
72
|
+
<input type="text" id="node-config-input-audience" placeholder="optional" style="width:70%;">
|
|
73
|
+
</div>
|
|
74
|
+
<div class="form-row auth-row-cognito" style="display:none;">
|
|
75
|
+
<label for="node-config-input-issuer"><i class="fa fa-building"></i> Issuer</label>
|
|
76
|
+
<input type="text" id="node-config-input-issuer" placeholder="optional" style="width:70%;">
|
|
77
|
+
</div>
|
|
78
|
+
<div class="auth-row-cognito help-text" style="display:none; font-size:12px; color:#666; margin: 4px 0 8px 110px; line-height:1.5;">
|
|
79
|
+
<b>JWKS URL</b> — public key endpoint of your Cognito User Pool:<br>
|
|
80
|
+
<code>https://cognito-idp.{region}.amazonaws.com/{pool-id}/.well-known/jwks.json</code><br>
|
|
81
|
+
<b>Audience</b> — App Client ID. Tokens issued to a different client are rejected.<br>
|
|
82
|
+
<b>Issuer</b> — User Pool base URL: <code>https://cognito-idp.{region}.amazonaws.com/{pool-id}</code><br>
|
|
83
|
+
<em>Note: Audience and Issuer are optional but strongly recommended.</em>
|
|
84
|
+
</div>
|
|
85
|
+
|
|
86
|
+
<div class="form-row auth-row-cognito" style="display:none;">
|
|
87
|
+
<input type="checkbox" id="node-config-input-exposeUser" style="display:inline-block; width:auto; vertical-align:top;">
|
|
88
|
+
<label for="node-config-input-exposeUser" style="width:auto;"> Expose user to flow (<code>msg.user</code>)</label>
|
|
89
|
+
</div>
|
|
90
|
+
<div class="form-row auth-row-cognito cognito-mapping-row" style="display:none;">
|
|
91
|
+
<label for="node-config-input-userMapping" style="vertical-align:top; margin-top:4px;"><i class="fa fa-user-o"></i> User mapping</label>
|
|
92
|
+
<textarea id="node-config-input-userMapping" rows="5"
|
|
93
|
+
style="width:70%; font-family:monospace; font-size:12px; resize:vertical;"
|
|
94
|
+
placeholder='{"id":"sub","email":"email","roles":"cognito:groups"}'></textarea>
|
|
95
|
+
</div>
|
|
96
|
+
<div class="auth-row-cognito cognito-mapping-row" style="display:none; font-size:12px; color:#666; margin: -4px 0 8px 110px; line-height:1.5;">
|
|
97
|
+
Map JWT payload fields into <code>msg.user</code>. Keys are the output field names, values are JWT claim names. The raw JWT token is never exposed in the flow — only the mapped fields are.<br>
|
|
98
|
+
<em>Example: <code>{"id":"sub","email":"email","roles":"cognito:groups"}</code></em>
|
|
99
|
+
</div>
|
|
100
|
+
</script>
|
|
101
|
+
|
|
102
|
+
<script type="text/javascript">
|
|
103
|
+
(function() {
|
|
104
|
+
RED.nodes.registerType('http.auth.config+', {
|
|
105
|
+
category: 'config',
|
|
106
|
+
color: "#9575CD",
|
|
107
|
+
defaults: {
|
|
108
|
+
name: { value: '' },
|
|
109
|
+
authType: { value: 'none' },
|
|
110
|
+
useJsonUsers: { value: false },
|
|
111
|
+
usersJson: { value: '' },
|
|
112
|
+
jwksUrl: { value: '' },
|
|
113
|
+
audience: { value: '' },
|
|
114
|
+
issuer: { value: '' },
|
|
115
|
+
exposeUser: { value: false },
|
|
116
|
+
userMapping: { value: '{\n "id": "sub",\n "email": "email",\n "roles": "cognito:groups"\n}' }
|
|
117
|
+
},
|
|
118
|
+
credentials: {
|
|
119
|
+
username: { type: 'text' },
|
|
120
|
+
password: { type: 'password' }
|
|
121
|
+
},
|
|
122
|
+
label: function() {
|
|
123
|
+
var labels = { none: 'None', basic: 'Basic Auth', cognito: 'Cognito JWT' };
|
|
124
|
+
return this.name || ('Auth: ' + (labels[this.authType] || this.authType));
|
|
125
|
+
},
|
|
126
|
+
oneditprepare: function() {
|
|
127
|
+
function updateSections() {
|
|
128
|
+
var type = $('#node-config-input-authType').val();
|
|
129
|
+
$('.auth-row-basic').toggle(type === 'basic');
|
|
130
|
+
$('.auth-row-cognito').toggle(type === 'cognito');
|
|
131
|
+
if (type === 'basic') {
|
|
132
|
+
updateBasicMode();
|
|
133
|
+
$('.cognito-mapping-row').hide();
|
|
134
|
+
} else if (type === 'cognito') {
|
|
135
|
+
$('.auth-simple-row').hide();
|
|
136
|
+
$('.auth-json-row').hide();
|
|
137
|
+
$('#auth-help-simple').hide();
|
|
138
|
+
$('#auth-help-json').hide();
|
|
139
|
+
updateCognitoMapping();
|
|
140
|
+
} else {
|
|
141
|
+
$('.auth-simple-row').hide();
|
|
142
|
+
$('.auth-json-row').hide();
|
|
143
|
+
$('#auth-help-simple').hide();
|
|
144
|
+
$('#auth-help-json').hide();
|
|
145
|
+
$('.cognito-mapping-row').hide();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function updateBasicMode() {
|
|
149
|
+
var isJson = $('#node-config-input-useJsonUsers').is(':checked');
|
|
150
|
+
$('.auth-simple-row').toggle(!isJson);
|
|
151
|
+
$('.auth-json-row').toggle(isJson);
|
|
152
|
+
$('#auth-help-simple').toggle(!isJson);
|
|
153
|
+
$('#auth-help-json').toggle(isJson);
|
|
154
|
+
}
|
|
155
|
+
function updateCognitoMapping() {
|
|
156
|
+
var expose = $('#node-config-input-exposeUser').is(':checked');
|
|
157
|
+
$('.cognito-mapping-row').toggle(expose);
|
|
158
|
+
}
|
|
159
|
+
$('#node-config-input-authType').on('change', updateSections);
|
|
160
|
+
$('#node-config-input-useJsonUsers').on('change', updateBasicMode);
|
|
161
|
+
$('#node-config-input-exposeUser').on('change', updateCognitoMapping);
|
|
162
|
+
updateSections();
|
|
163
|
+
setTimeout(updateSections, 0);
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
})();
|
|
167
|
+
</script>
|