homebridge-openwrt-control 0.3.6 → 0.3.8

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
@@ -9,6 +9,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
9
9
 
10
10
  - After update to v0.1.0 the plugin need to be reconfigured!
11
11
 
12
+ ## [0.3.8] - (23.09.2026)
13
+
14
+ ### Added
15
+
16
+ - optional RESTful access `Token`. When set, every request (GET and POST) must send the header `Authorization: Bearer <token>`, otherwise the server responds `401`. Leaving it empty keeps the previous behaviour, so existing configs keep working
17
+ - readme update
18
+
12
19
  ## [0.3.6] - (11.05.2026)
13
20
 
14
21
  ## Changes
package/README.md CHANGED
@@ -99,6 +99,7 @@
99
99
  | `restFul{}` | RESTFul object. |
100
100
  | `restFul.enable` | If enabled, RESTful server will start automatically and respond to any path request. |
101
101
  | `restFul.port` | Here set the listening `Port` for RESTful server. |
102
+ | `restFul.token` | Here optional set the access token. When set, every request must send the header `Authorization: Bearer <token>`, otherwise the server responds `401`. |
102
103
  | `mqtt{}` | MQTT object. |
103
104
  | `mqtt.enable` | If enabled, MQTT Broker will start automatically and publish all available data. |
104
105
  | `mqtt.host` | Here set the `IP Address` or `Hostname` for MQTT Broker. |
@@ -113,6 +114,7 @@
113
114
  ### REST Integration
114
115
 
115
116
  REST POST calls must include a content-type header of `application/json`.
117
+ If `Token` is set, every request (GET and POST) must send the header `Authorization: Bearer <token>`.
116
118
  Path `status` response all available paths.
117
119
 
118
120
  | Method | URL | Path | Response | Type |
@@ -478,6 +478,16 @@
478
478
  "condition": {
479
479
  "functionBody": "return model.devices[arrayIndices].restFul.enable == true;"
480
480
  }
481
+ },
482
+ "token": {
483
+ "title": "Token",
484
+ "type": "string",
485
+ "placeholder": "optional",
486
+ "description": "Optional access token. When set, every request must send the header 'Authorization: Bearer <token>'. Leave empty to keep the server open on the local network.",
487
+ "format": "password",
488
+ "condition": {
489
+ "functionBody": "return model.devices[arrayIndices].restFul.enable == true;"
490
+ }
481
491
  }
482
492
  }
483
493
  },
@@ -782,7 +792,8 @@
782
792
  "title": "RESTFul",
783
793
  "items": [
784
794
  "devices[].restFul.enable",
785
- "devices[].restFul.port"
795
+ "devices[].restFul.port",
796
+ "devices[].restFul.token"
786
797
  ]
787
798
  },
788
799
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "displayName": "OpenWrt Control",
3
3
  "name": "homebridge-openwrt-control",
4
- "version": "0.3.6",
4
+ "version": "0.3.8",
5
5
  "description": "Homebridge plugin to control OpenWrt flashed devices.",
6
6
  "license": "MIT",
7
7
  "author": "grzegorz914",
@@ -34,8 +34,8 @@
34
34
  "node": "^20 || ^22 || ^24 || ^25 || ^26"
35
35
  },
36
36
  "dependencies": {
37
- "mqtt": "^5.15.1",
38
- "axios": "^1.16.0",
37
+ "mqtt": "^5.15.2",
38
+ "axios": "^1.20.0",
39
39
  "express": "^5.2.1"
40
40
  },
41
41
  "keywords": [
package/src/restful.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import express, { json } from 'express';
2
2
  import EventEmitter from 'events';
3
+ import { createHash, timingSafeEqual } from 'crypto';
3
4
 
4
5
  const DEFAULT_MESSAGE = 'This data is not available at this time.';
5
6
 
@@ -7,6 +8,7 @@ class RestFul extends EventEmitter {
7
8
  constructor(config) {
8
9
  super();
9
10
  this.port = config.port;
11
+ this.token = typeof config.token === 'string' ? config.token.trim() : '';
10
12
  this.logWarn = config.logWarn;
11
13
  this.logDebug = config.logDebug;
12
14
 
@@ -23,6 +25,19 @@ class RestFul extends EventEmitter {
23
25
  app.set('json spaces', 2);
24
26
  app.use(json());
25
27
 
28
+ // Optional token auth, when a token is configured every route requires "Authorization: Bearer <token>"
29
+ if (this.token) {
30
+ app.use((req, res, next) => {
31
+ const header = req.headers.authorization ?? '';
32
+ const provided = header.startsWith('Bearer ') ? header.slice(7) : '';
33
+ if (!this.tokenValid(provided)) {
34
+ if (this.logWarn) this.emit('warn', `RESTFul Unauthorized request from: ${req.ip}, ${req.method} ${req.path}`);
35
+ return res.status(401).json({ error: 'RESTFul Unauthorized' });
36
+ }
37
+ next();
38
+ });
39
+ }
40
+
26
41
  // Register GET routes for all keys
27
42
  for (const key of Object.keys(this.restFulData)) {
28
43
  app.get(`/${key}`, (req, res) => {
@@ -71,6 +86,13 @@ class RestFul extends EventEmitter {
71
86
  }
72
87
  }
73
88
 
89
+ tokenValid(provided) {
90
+ // Compare fixed-length hashes so the check takes the same time regardless of token length or content
91
+ const a = createHash('sha256').update(provided).digest();
92
+ const b = createHash('sha256').update(this.token).digest();
93
+ return timingSafeEqual(a, b);
94
+ }
95
+
74
96
  update(path, data) {
75
97
  if (Object.hasOwn(this.restFulData, path)) {
76
98
  this.restFulData[path] = data;
package/src/router.js CHANGED
@@ -185,6 +185,7 @@ class Router extends EventEmitter {
185
185
  if (this.restFul.enable) {
186
186
  this.restFul1 = new RestFul({
187
187
  port: this.restFul.port || 3000,
188
+ token: this.restFul.token,
188
189
  logWarn: this.logWarn,
189
190
  logDebug: this.logDebug
190
191
  })