Skip to content

Commit

Permalink
Merge pull request #191 from flowforge/190-add-middleware-support
Browse files Browse the repository at this point in the history
Add support for settings-provided middleware
  • Loading branch information
joepavitt authored Sep 14, 2023
2 parents 2800f28 + 465d629 commit ca36a7e
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 4 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export default {
text: 'User Guides',
collapsed: false,
items: [
{ text: 'Settings', link: '/user/settings' },
{ text: 'Dynamic Properties', link: '/user/dynamic-properties' }
]
},
Expand Down
78 changes: 78 additions & 0 deletions docs/user/settings.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Settings

Whilst most dashboard configuration is done within the Node-RED editor, some settings
can be provided in the [Node-RED runtime settings file](https://nodered.org/docs/user-guide/runtime/settings-file).

The `settings.js` file is typically located in the `~/.node-red` directory. If you
are not sure where that is, you can also check the log output of Node-RED when it starts.
For example:

```
12 Sep 13:31:37 - [info] Settings file : /Users/nol/.node-red/settings.js
```

The file is structured as a JavaScript module:

```js
module.exports = {
// Lots of settings...
}
```

Node-RED Dashboard 2.0 looks for a property called `dashboard` within the settings object.
If it doesn't find one, it looks for a property called `ui` - this is the settings object
used by the original Node-RED Dashboard. This eases the migration between the two dashboard
versions.

Edit the `settings.js` file and add a `dashboard` property inside the `module.exports` object.
This needs to be separated from any other setting with a comma:

```js
dashboard: {

}
```

You can then add Dashboard specific settings inside that property. The available
settings are described below.

Whenever you make any changes to the `settings.js` file, you will need to restart
Node-RED to load those changes.

### Dashboard Settings

The following settings are available.

#### `middleware`

This adds a custom [Express](https://expressjs.com/) middleware in front of the dashboard.
This can be used to apply custom authentication, logging or any other type of processing
ahead of any request to the dashboard.


```js
dashboard: {
middleware: (request, response, next) => {
console.log(`New dashboard request from ${request.ip} to ${request.path}`)
next()
}
}
```

#### `ioMiddleware`

This adds a custom [Socket.IO middleware](https://socket.io/docs/v4/middlewares/) in front
of the websocket connection between the Dashboard page and Node-RED.

```js
dashboard: {
ioMiddleware: (socket, next) => {
if (isValid(socket.request)) {
next();
} else {
next(new Error("invalid"));
}
}
}
```

40 changes: 36 additions & 4 deletions nodes/config/ui_base.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ module.exports = function (RED) {
/** @type { Server } */
ioServer: null,
/** @type {Object.<string, Socket>} */
connections: {}
connections: {},
settings: {}
}

/**
Expand All @@ -42,17 +43,28 @@ module.exports = function (RED) {
uiShared.app = RED.httpNode || RED.httpAdmin
uiShared.httpServer = RED.server

// Use the 'dashboard' settings if present, otherwise fallback
// to node-red-dashboard 'ui' settings object.
uiShared.settings = RED.settings.dashboard || RED.settings.ui || {}

// Default no-op middleware
uiShared.httpMiddleware = function (req, res, next) { next() }
if (uiShared.settings.middleware) {
if (typeof uiShared.settings.middleware === 'function' || Array.isArray(uiShared.settings.middleware)) {
uiShared.httpMiddleware = uiShared.settings.middleware
}
}
/**
* Configure Web Server to handle UI traffic
*/

uiShared.app.use(config.path, express.static(path.join(__dirname, '../../dist')))
uiShared.app.use(config.path, uiShared.httpMiddleware, express.static(path.join(__dirname, '../../dist')))

uiShared.app.get(config.path, (req, res) => {
uiShared.app.get(config.path, uiShared.httpMiddleware, (req, res) => {
res.sendFile(path.join(__dirname, '../../dist/index.html'))
})

uiShared.app.get(config.path + '/*', (req, res) => {
uiShared.app.get(config.path + '/*', uiShared.httpMiddleware, (req, res) => {
res.sendFile(path.join(__dirname, '../../dist/index.html'))
})

Expand All @@ -74,6 +86,26 @@ module.exports = function (RED) {
uiShared.ioServer = new Server(uiShared.httpServer, serverOptions)
uiShared.ioServer.setMaxListeners(0) // prevent memory leak warning // TODO: be more smart about this!

if (typeof uiShared.settings.ioMiddleware === 'function') {
uiShared.ioServer.use(uiShared.settings.ioMiddleware)
} else if (Array.isArray(uiShared.settings.ioMiddleware)) {
uiShared.settings.ioMiddleware.forEach(function (ioMiddleware) {
uiShared.ioServer.use(ioMiddleware)
})
} else {
uiShared.ioServer.use(function (socket, next) {
if (socket.client.conn.request.url.indexOf('transport=websocket') !== -1) {
// Reject direct websocket requests
socket.client.conn.close()
return
}
if (socket.handshake.xdomain === false) {
return next()
} else {
socket.disconnect(true)
}
})
}
const bindOn = RED.server ? 'bound to Node-RED port' : 'on port ' + node.port
node.log('Created socket.io server ' + bindOn + ' at path ' + socketIoPath)
} else {
Expand Down

0 comments on commit ca36a7e

Please sign in to comment.