@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/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='{&#10; "admin": "password123",&#10; "user1": "abc123"&#10;}'
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>