- **Image**: built locally (`cloud-node-red:4.1`) — upstream Node-RED + `passport-openidconnect`. See `Dockerfile`.
- **Config**: `config/settings.js` (mounted to `/data/settings.js`)
## Auth
The **editor** (not the runtime HTTP-in / dashboard nodes) is gated by Keycloak OIDC via `passport-openidconnect`.
### Implementation notes
Node-RED's `adminAuth.type=strategy` mode doesn't give the passport strategy access to `req.session`, so we can't use passport-openidconnect's default `SessionStore`. `config/settings.js` provides an **in-memory state store** keyed by a random handle (10-minute TTL) — handles are issued at the `/auth/strategy` redirect and consumed at the callback. State survives across the OAuth round trip without needing express-session.
Verify returns `{username: <preferred_username>}`; the `adminAuth.users(username)` resolver then expands that to a Node-RED user object with permissions.
### Permissions
Set `NODERED_ADMIN_USERS` (comma-separated usernames or emails) in `.env`:
| User | permissions |
|---|---|
| listed in `NODERED_ADMIN_USERS` | `"*"` (full editor) |
| any other authenticated realm user | `["read"]` (can view flows, cannot deploy) |
Switching this to a Keycloak `app-admin` realm-role check is a TODO.
## TODO
- Switch admin lookup from env-list to `app-admin` realm role
- Gate runtime HTTP-in / dashboard routes with `httpNodeAuth` if/when we expose them publicly
- Preinstalled module list (`packages.json`) — mqtt, influxdb, postgres clients
- Set `credentialSecret` in settings.js (currently auto-generated; rotating it forces re-entering creds)