@com55/node-red-ai-access 0.1.1 → 0.2.0-beta.2

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 CHANGED
@@ -6,6 +6,78 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to
7
7
  [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8
8
 
9
+ ## Unreleased
10
+
11
+ ## [0.2.0-beta.2] - 2026-10-28
12
+
13
+ ### Fixed
14
+
15
+ - OAuth route collision check no longer counts disabled `http in` nodes or nodes
16
+ on disabled flows; collisions are re-evaluated from scratch on every deploy,
17
+ and an enabled `http in` node is reported once (not again as an Express "bound
18
+ dispatch" route).
19
+ - OAuth route collision check ignores `http in` nodes in subflow templates
20
+ without a running instance, compares real mounted paths (`httpAdminRoot` /
21
+ `httpNodeRoot`), and matches paths case-insensitively like Express.
22
+ - `GET /mcp/api/v1/oauth/status` no longer exposes absolute `httpStatic`
23
+ filesystem paths to users without full (`*`) permission.
24
+ - Editor sidebar: OAuth lifetime number inputs and unit dropdowns sit on one
25
+ line, including the narrow Node-RED 5 sidebar.
26
+ - Editor sidebar: OAuth route collisions are shown as a readable bulleted list,
27
+ one route per line with paths in `<code>`; `http in` nodes link to the node in
28
+ the editor.
29
+ - Editor sidebar: Public URL "Check" result is shown as a clear green/red
30
+ status; loopback notes and the trust proxy state are moved into a muted,
31
+ collapsible "About the check" section with human-readable wording.
32
+ - OAuth route collision check also honours `httpStaticRoot` (and per-entry
33
+ `httpStatic` roots).
34
+ - `/mcp` page Access links: the area under the form now always shows generic
35
+ usage (with a `<YOUR_TOKEN>` placeholder) for the currently selected channel
36
+ and updates when the channel changes. The real token and client setup are
37
+ shown once in a modal right after creation; closing it removes the token from
38
+ the page.
39
+ - Access link create/list responses are sent with `Cache-Control: no-store` and
40
+ `Pragma: no-cache`.
41
+ - Access link tokens are kept in memory as a SHA-256 hash only (the raw token is
42
+ no longer retained after creation).
43
+
44
+ ### Breaking
45
+
46
+ - **Access tokens can no longer be viewed or copied after creation:**
47
+ `GET /mcp/api/v1/links` no longer returns `token` (rest/mcp) or `url`
48
+ (get_link), and the links table only offers Revoke. Revoke and create a new
49
+ token instead.
50
+
51
+ ### Added
52
+
53
+ - Editor sidebar: short "How to use MCP OAuth" help in the OAuth section.
54
+ - `GET /mcp/api/v1/oauth/status` returns structured `details` (path, source,
55
+ node id/name) for each collision.
56
+
57
+ ## [0.2.0-beta.1] - 2026-10-28
58
+
59
+ First pre-release of 0.2.0, published under the npm `beta` dist-tag.
60
+
61
+ ### Added
62
+
63
+ - Node-RED settings and sidebar for configuring AI Access (#91).
64
+ - MCP OAuth authorization (#99): dynamic client registration (DCR), PKCE, an
65
+ owner consent page, and sliding refresh tokens with defaults of 1 hour (access
66
+ token), 30 days (refresh idle) and 90 days (refresh maximum).
67
+ - Route-collision check for the OAuth and metadata endpoints.
68
+ - MCP OAuth 2.1 authorization server and protected resource support with PKCE
69
+ S256, dynamic client registration, resource-bound tokens, rotating refresh
70
+ tokens, and `read`/`propose` scopes. Existing access-link tokens still work.
71
+ - Sidebar controls for OAuth lifetimes and client limits, registered-client
72
+ revocation, route-collision and metadata-fallback warnings, and a Public URL
73
+ warning when enabling OAuth, plus a refresh-token maximum lifetime (default 90
74
+ days, 0 = unlimited).
75
+
76
+ ### Changed
77
+
78
+ - CI now tests against a Node-RED 4 and Node-RED 5 matrix.
79
+ - Requires Node.js >= 20.19.
80
+
9
81
  ## [0.1.1] - 2026-10-17
10
82
 
11
83
  ### Added
@@ -384,5 +456,7 @@ Requires Node.js 20+.
384
456
  auth, 413 limits) served at `GET /mcp/api/v1/openapi.yaml`; README install,
385
457
  config, `/mcp` usage, and AI quickstart.
386
458
 
459
+ [0.2.0-beta.1]:
460
+ https://github.com/com55/node-red-ai-access/compare/v0.1.1...v0.2.0-beta.1
387
461
  [0.1.1]: https://github.com/com55/node-red-ai-access/compare/v0.1.0...v0.1.1
388
462
  [0.1.0]: https://github.com/com55/node-red-ai-access/releases/tag/v0.1.0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Node-RED AI Access
2
2
 
3
3
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
- [![Node.js Version](https://img.shields.io/badge/node-%3E%3D22.0.0-brightgreen.svg)](https://nodejs.org/)
4
+ [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20.19.0-brightgreen.svg)](https://nodejs.org/)
5
5
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.7%2B-blue.svg)](https://www.typescriptlang.org/)
6
6
  [![CI](https://github.com/com55/node-red-ai-access/actions/workflows/ci.yml/badge.svg)](https://github.com/com55/node-red-ai-access/actions)
7
7
 
@@ -24,9 +24,10 @@ Derived from
24
24
 
25
25
  ## Install (Node-RED plugin)
26
26
 
27
- Requirements: **Node-RED 4.x or newer**, Node.js 20+, and **`adminAuth`
28
- enabled** (recommended). Without `adminAuth`, AI features stay off unless you
29
- opt in (see Configuration).
27
+ Requirements: **Node-RED 4.x or newer**, Node.js 20.19+ (tested: Node-RED 4.1 on
28
+ Node 20/22, Node-RED 5.0 on Node 22/24), and **`adminAuth` enabled**
29
+ (recommended). Without `adminAuth`, AI features stay off unless you opt in (see
30
+ Configuration).
30
31
 
31
32
  1. Install into your Node-RED user directory (or via the palette):
32
33
 
@@ -80,6 +81,13 @@ module.exports = {
80
81
  // Browser origins allowed to call /mcp/a, /mcp/api, and /mcp/rpc
81
82
  // cross-origin (default: none — no CORS headers are sent)
82
83
  // corsAllowedOrigins: ['https://app.example.com'],
84
+ // MCP OAuth (disabled by default; publicBaseUrl is the issuer)
85
+ // oauth: {
86
+ // enabled: true,
87
+ // accessTokenTtlSec: 3600,
88
+ // refreshIdleTtlSec: 2592000,
89
+ // dcrMaxClients: 20,
90
+ // },
83
91
  },
84
92
  };
85
93
  ```
@@ -89,8 +97,10 @@ module.exports = {
89
97
  Access-link `url`, proposal `reviewUrl`, and `accessLinksUrl` (in
90
98
  `token_expired`) are absolute URLs built by one shared helper:
91
99
 
92
- 1. `aiAccess.publicBaseUrl`, when set, is used as-is (scheme, host, and an
93
- optional path prefix). It is read from `settings.js` only.
100
+ 1. Effective `aiAccess.publicBaseUrl`, when set, overrides the request origin.
101
+ The sidebar/settings API accepts an HTTPS origin without a path, query,
102
+ fragment, or credentials (HTTP localhost / loopback is allowed for
103
+ development). An empty value uses the request origin.
94
104
  2. Otherwise, when Express `trust proxy` is enabled (for example
95
105
  `httpServerOptions: { 'trust proxy': true }`), the first `X-Forwarded-Proto`
96
106
  and `X-Forwarded-Host` values are used.
@@ -117,34 +127,82 @@ affected by this setting.
117
127
  By default the AI-facing endpoints (`/mcp/a/*`, `/mcp/api/*`, `/mcp/rpc`) send
118
128
  **no** CORS headers, overriding Node-RED's built-in
119
129
  `Access-Control-Allow-Origin: *` for those paths. To allow a browser app, list
120
- its origin in `aiAccess.corsAllowedOrigins` (`settings.js` only, restart
121
- required). For a listed `Origin` the response echoes it in
122
- `Access-Control-Allow-Origin` with `Vary: Origin`, and `OPTIONS` preflight
123
- returns `204` with allowed methods (`GET, POST, DELETE, OPTIONS`) and headers
124
- (`Authorization`, `Content-Type`, `Accept`, `Mcp-Session-Id`,
125
- `Mcp-Protocol-Version`, `Last-Event-ID`). Origins match case-insensitively and
126
- the configured origin is echoed; malformed origins are rejected. Exposed headers
127
- include `Mcp-Session-Id`, `WWW-Authenticate`, `Retry-After`, and the
128
- `RateLimit*` headers. Other origins get no CORS headers.
130
+ its origin in `aiAccess.corsAllowedOrigins` via the AI Access sidebar or
131
+ settings API. Changes apply immediately. The API rejects any invalid origin in
132
+ the supplied list (including paths, credentials, queries, and fragments). For a
133
+ listed `Origin` the response echoes it in `Access-Control-Allow-Origin` with
134
+ `Vary: Origin`, and `OPTIONS` preflight returns `204` with allowed methods
135
+ (`GET, POST, DELETE, OPTIONS`) and headers (`Authorization`, `Content-Type`,
136
+ `Accept`, `Mcp-Session-Id`, `Mcp-Protocol-Version`, `Last-Event-ID`). Origins
137
+ match case-insensitively and the configured origin is echoed; malformed origins
138
+ are rejected. Exposed headers include `Mcp-Session-Id`, `WWW-Authenticate`,
139
+ `Retry-After`, and the `RateLimit*` headers. Other origins get no CORS headers.
129
140
 
130
141
  ### Settings precedence
131
142
 
132
143
  Effective values are resolved **without restart** in this order (highest wins):
133
144
 
134
- 1. **Disk** — `<userDir>/.ai-access/settings.json` (editor sidebar **AI Access**
145
+ 1. **`settings.js`** — keys present in the `aiAccess` block are locked
146
+ 2. **Disk** — `<userDir>/.ai-access/config.json` (editor sidebar **AI Access**
135
147
  tab, or `PUT /mcp/api/v1/settings`)
136
- 2. **`settings.js`** — `aiAccess` block (install / bootstrap defaults)
137
148
  3. **Built-in defaults** — as documented above
138
149
 
139
- Only keys present on disk override; omitted keys fall through to `settings.js`,
140
- then defaults. The editor right-hand sidebar tab (**AI Access**, next to
141
- Information / Help / Debug / Configuration nodes / Context Data) lets `*` admins
142
- change enable/disable, default deploy type, and default access-link TTL; `read`
143
- users see the same values read-only. A button opens the `/mcp` page. When
150
+ This is a breaking precedence change in 0.2.0: settings.js now wins over disk.
151
+ Legacy `settings.json` is read when `config.json` is absent; the next save
152
+ writes `config.json`. Locked fields show a lock indicator and disabled input.
153
+ PUT ignores locked keys while applying unlocked ones; GET and PUT return
154
+ per-field `sources` (`settings.js`, `disk`, or `default`) and the precedence
155
+ order.
156
+
157
+ The editor right-hand sidebar tab (**AI Access**, next to Information / Help /
158
+ Debug / Configuration nodes / Context Data) lets `*` admins change
159
+ enable/disable, default deploy type, access-link TTL, maximum active links,
160
+ Public URL, CORS origins, and the three rate limits. `read` users see the same
161
+ values read-only. Express's effective `trust proxy` setting is displayed
162
+ read-only. Public URL, CORS, and rate limits apply immediately; changing limits
163
+ starts fresh rate-limit buckets. A button opens the `/mcp` page. When
144
164
  `adminAuth` is off, the tab exposes the `allowWithoutAdminAuth` opt-in with a
145
165
  clear local-only warning.
146
166
 
147
- Storage lives under **`<userDir>/.ai-access`** (`settings.json`,
167
+ The sidebar also configures MCP OAuth, shows registered clients with revoke
168
+ buttons, and reports route collisions and the `httpNodeRoot: false` metadata
169
+ fallback. Enabling OAuth with no Public URL or an HTTP non-loopback URL shows a
170
+ warning confirmation; it does not block the change. OAuth fields set in
171
+ `settings.js` are locked in the sidebar.
172
+
173
+ ### MCP OAuth
174
+
175
+ OAuth is disabled by default. Set `aiAccess.publicBaseUrl` to the public HTTPS
176
+ origin before enabling it; localhost and loopback HTTP are supported for local
177
+ development. Node-RED serves authorization, token, dynamic client registration,
178
+ and revocation under `/mcp/oauth/*`. It publishes authorization server metadata
179
+ at `/.well-known/oauth-authorization-server` and protected resource metadata at
180
+ `/.well-known/oauth-protected-resource/mcp` and
181
+ `/.well-known/oauth-protected-resource`. The issuer is `publicBaseUrl` and the
182
+ resource indicator is `{publicBaseUrl}/mcp`. When `httpNodeRoot` is false,
183
+ metadata is available under the admin `/mcp` path instead; the sidebar displays
184
+ this fallback. Route collisions disable the affected OAuth endpoints and appear
185
+ in the sidebar.
186
+
187
+ Clients use authorization code with PKCE S256 and can request `read` or
188
+ `propose`. Consent uses the Node-RED editor login. Access tokens last one hour
189
+ by default; refresh tokens expire after 30 days of inactivity and rotate on each
190
+ use. Reusing a rotated refresh token revokes its token family. Tokens are stored
191
+ as SHA-256 hashes in `<userDir>/.ai-access/oauth.json`. Access-link `aia_`
192
+ bearer tokens continue to work. See [MCP client setup](docs/mcp-clients.md).
193
+
194
+ Public URL **Check** uses `POST /mcp/api/v1/settings/check-public-url` and
195
+ requires `*` permission because it performs an outbound request. It validates
196
+ the form URL, fetches only `{publicBaseUrl}{httpAdminRoot}/mcp/api/v1`
197
+ (discovery), times out after 5 seconds, and never follows redirects. The result
198
+ reports the HTTP status or a connection/timeout error; it does not save the
199
+ form. Loopback checks use Node-RED's own listening port; behind a reverse proxy,
200
+ use the internal Node-RED port. Unix-socket listeners are not supported. HTTPS
201
+ checks using a self-signed certificate fail because TLS verification remains
202
+ enabled. Saving a Public URL with a private/internal IP literal is allowed, but
203
+ the settings response and sidebar show a warning.
204
+
205
+ Storage lives under **`<userDir>/.ai-access`** (`config.json`,
148
206
  `proposals/*.json`, and append-only `logs/audit.jsonl`). Directories use mode
149
207
  `0700`, files `0600`. Proposal JSON is capped at **1_500_000 bytes**; oversized
150
208
  proposal JSON or audit lines return HTTP **413** with `code: payload_too_large`
@@ -229,9 +287,9 @@ a `code`. `token_expired` also includes `expiredAt` and `accessLinksUrl` (the
229
287
 
230
288
  Access links are **in memory** (default TTL **4h**, max 10). Caps: **get_link ≤
231
289
  24h**, **rest/mcp ≤ 30d**. Creating a link returns the raw `token` plus a
232
- self-contained `url` for `get_link` (`/mcp/a/<token>`). While the link remains
233
- active, `GET /links` includes `url` again so admins can re-copy from `/mcp` (the
234
- raw `token` field stays create-only). After a restart every old token is
290
+ self-contained `url` for `get_link` (`/mcp/a/<token>`). The token is shown only
291
+ once: `GET /links` never returns `token` or `url`, so to give access again
292
+ revoke the link and create a new one. After a restart every old token is
235
293
  invalid.
236
294
 
237
295
  Call `list_flows` or `get_flow` first and pass the returned `rev` as
package/ai-access.html CHANGED
@@ -14,7 +14,7 @@
14
14
  }
15
15
  window.__com55AiAccessSidebarLoading = true;
16
16
 
17
- var EDITOR_SIDEBAR_ASSET_VERSION = '0.1.1';
17
+ var EDITOR_SIDEBAR_ASSET_VERSION = '0.2.0-beta.2';
18
18
  var root = (RED.settings.httpAdminRoot || '/').replace(/\/$/, '');
19
19
  var src =
20
20
  (root || '') +