smart-home-engine 1.16.1 → 1.17.1
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/dist/web/assets/{index-B3w0pv6a.css → index-BvKmwc2a.css} +1 -1
- package/dist/web/assets/index-CvkGKRP_.js +234 -0
- package/dist/web/assets/{tsMode-DuWnr8LR.js → tsMode-CUj-T2Hb.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/src/web/broker-api.js +144 -0
- package/dist/web/assets/index-BIE09pOh.js +0 -232
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{m as O}from"./monaco-langs-Dt8a9eeo.js";import{t as I}from"./index-
|
|
1
|
+
import{m as O}from"./monaco-langs-Dt8a9eeo.js";import{t as I}from"./index-CvkGKRP_.js";/*!-----------------------------------------------------------------------------
|
|
2
2
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
3
3
|
* Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
|
|
4
4
|
* Released under the MIT license
|
package/dist/web/index.html
CHANGED
|
@@ -172,10 +172,10 @@
|
|
|
172
172
|
}
|
|
173
173
|
})();
|
|
174
174
|
</script>
|
|
175
|
-
<script type="module" crossorigin src="/assets/index-
|
|
175
|
+
<script type="module" crossorigin src="/assets/index-CvkGKRP_.js"></script>
|
|
176
176
|
<link rel="modulepreload" crossorigin href="/assets/monaco-langs-Dt8a9eeo.js">
|
|
177
177
|
<link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
|
|
178
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
178
|
+
<link rel="stylesheet" crossorigin href="/assets/index-BvKmwc2a.css">
|
|
179
179
|
</head>
|
|
180
180
|
<body>
|
|
181
181
|
<div id="app"></div>
|
package/package.json
CHANGED
package/src/web/broker-api.js
CHANGED
|
@@ -950,6 +950,150 @@ router.get('/logs', (req, res) => {
|
|
|
950
950
|
res.json(limit >= all.length ? all : all.slice(-limit));
|
|
951
951
|
});
|
|
952
952
|
|
|
953
|
+
// ── Password file management ──────────────────────────────────────────────────
|
|
954
|
+
|
|
955
|
+
/** POSIX shell-safe single-quote escaping for SSH commands */
|
|
956
|
+
function shellArg(s) {
|
|
957
|
+
return "'" + String(s).replace(/'/g, "'\\''") + "'";
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* GET /she/broker/passwd?file=<path>
|
|
962
|
+
* List usernames from a mosquitto password file.
|
|
963
|
+
*/
|
|
964
|
+
router.get('/passwd', async (req, res) => {
|
|
965
|
+
try {
|
|
966
|
+
const bc = getBrokerConfig(req);
|
|
967
|
+
const file = String(req.query.file || '').trim();
|
|
968
|
+
if (!file || !file.startsWith('/')) return res.status(400).json({ error: 'absolute file path required' });
|
|
969
|
+
|
|
970
|
+
let text = '';
|
|
971
|
+
if (bc.ssh && bc.ssh.host) {
|
|
972
|
+
const result = await sshDeploy.runCommand(bc.ssh, `sudo cat -- ${shellArg(file)} 2>/dev/null || true`);
|
|
973
|
+
text = result.stdout;
|
|
974
|
+
} else {
|
|
975
|
+
try {
|
|
976
|
+
text = fs.readFileSync(file, 'utf8');
|
|
977
|
+
} catch (e) {
|
|
978
|
+
if (e.code !== 'ENOENT') throw e;
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
const users = text
|
|
982
|
+
.split('\n')
|
|
983
|
+
.filter((l) => l.includes(':'))
|
|
984
|
+
.map((l) => l.split(':')[0])
|
|
985
|
+
.filter(Boolean);
|
|
986
|
+
res.json({ users });
|
|
987
|
+
} catch (err) {
|
|
988
|
+
handleError(res, err);
|
|
989
|
+
}
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* POST /she/broker/passwd
|
|
994
|
+
* Add or update a user in a mosquitto password file.
|
|
995
|
+
* Body: { file, username, password }
|
|
996
|
+
*/
|
|
997
|
+
router.post('/passwd', async (req, res) => {
|
|
998
|
+
try {
|
|
999
|
+
const bc = getBrokerConfig(req);
|
|
1000
|
+
const { file, username, password } = req.body;
|
|
1001
|
+
if (!file || typeof file !== 'string' || !file.startsWith('/')) return res.status(400).json({ error: 'absolute file path required' });
|
|
1002
|
+
if (!username || typeof username !== 'string') return res.status(400).json({ error: 'username required' });
|
|
1003
|
+
if (!password || typeof password !== 'string') return res.status(400).json({ error: 'password required' });
|
|
1004
|
+
if (/[:\n\r]/.test(username)) return res.status(400).json({ error: 'invalid username' });
|
|
1005
|
+
|
|
1006
|
+
if (bc.ssh && bc.ssh.host) {
|
|
1007
|
+
const cmd = `sudo mosquitto_passwd -b ${shellArg(file)} ${shellArg(username)} ${shellArg(password)}`;
|
|
1008
|
+
await sshDeploy.runCommand(bc.ssh, cmd);
|
|
1009
|
+
} else {
|
|
1010
|
+
await execFileAsync('mosquitto_passwd', ['-b', file, username, password]);
|
|
1011
|
+
}
|
|
1012
|
+
res.json({ ok: true });
|
|
1013
|
+
} catch (err) {
|
|
1014
|
+
handleError(res, err);
|
|
1015
|
+
}
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
/**
|
|
1019
|
+
* DELETE /she/broker/passwd/:user
|
|
1020
|
+
* Delete a user from a mosquitto password file.
|
|
1021
|
+
* Body: { file }
|
|
1022
|
+
*/
|
|
1023
|
+
router.delete('/passwd/:user', async (req, res) => {
|
|
1024
|
+
try {
|
|
1025
|
+
const bc = getBrokerConfig(req);
|
|
1026
|
+
const { file } = req.body;
|
|
1027
|
+
const username = req.params.user;
|
|
1028
|
+
if (!file || typeof file !== 'string' || !file.startsWith('/')) return res.status(400).json({ error: 'absolute file path required' });
|
|
1029
|
+
|
|
1030
|
+
if (bc.ssh && bc.ssh.host) {
|
|
1031
|
+
const cmd = `sudo mosquitto_passwd -D ${shellArg(file)} ${shellArg(username)}`;
|
|
1032
|
+
await sshDeploy.runCommand(bc.ssh, cmd);
|
|
1033
|
+
} else {
|
|
1034
|
+
await execFileAsync('mosquitto_passwd', ['-D', file, username]);
|
|
1035
|
+
}
|
|
1036
|
+
res.json({ ok: true });
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
handleError(res, err);
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
// ── ACL file management ───────────────────────────────────────────────────────
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* GET /she/broker/acl?file=<path>
|
|
1046
|
+
* Read a mosquitto ACL file (static file, not dynsec).
|
|
1047
|
+
*/
|
|
1048
|
+
router.get('/acl', async (req, res) => {
|
|
1049
|
+
try {
|
|
1050
|
+
const bc = getBrokerConfig(req);
|
|
1051
|
+
const file = String(req.query.file || '').trim();
|
|
1052
|
+
if (!file || !file.startsWith('/')) return res.status(400).json({ error: 'absolute file path required' });
|
|
1053
|
+
|
|
1054
|
+
let content = '';
|
|
1055
|
+
if (bc.ssh && bc.ssh.host) {
|
|
1056
|
+
try {
|
|
1057
|
+
content = await sshDeploy.readRemoteFile(bc.ssh, file);
|
|
1058
|
+
} catch (e) {
|
|
1059
|
+
if (!String(e.message).toLowerCase().includes('no such file')) throw e;
|
|
1060
|
+
}
|
|
1061
|
+
} else {
|
|
1062
|
+
try {
|
|
1063
|
+
content = fs.readFileSync(file, 'utf8');
|
|
1064
|
+
} catch (e) {
|
|
1065
|
+
if (e.code !== 'ENOENT') throw e;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
res.json({ content });
|
|
1069
|
+
} catch (err) {
|
|
1070
|
+
handleError(res, err);
|
|
1071
|
+
}
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
/**
|
|
1075
|
+
* PUT /she/broker/acl
|
|
1076
|
+
* Write a mosquitto ACL file.
|
|
1077
|
+
* Body: { file, content }
|
|
1078
|
+
*/
|
|
1079
|
+
router.put('/acl', async (req, res) => {
|
|
1080
|
+
try {
|
|
1081
|
+
const bc = getBrokerConfig(req);
|
|
1082
|
+
const { file, content } = req.body;
|
|
1083
|
+
if (!file || typeof file !== 'string' || !file.startsWith('/')) return res.status(400).json({ error: 'absolute file path required' });
|
|
1084
|
+
if (typeof content !== 'string') return res.status(400).json({ error: 'content must be a string' });
|
|
1085
|
+
|
|
1086
|
+
if (bc.ssh && bc.ssh.host) {
|
|
1087
|
+
await sshDeploy.uploadContent(bc.ssh, content, file);
|
|
1088
|
+
} else {
|
|
1089
|
+
fs.writeFileSync(file, content, 'utf8');
|
|
1090
|
+
}
|
|
1091
|
+
res.json({ ok: true });
|
|
1092
|
+
} catch (err) {
|
|
1093
|
+
handleError(res, err);
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
|
|
953
1097
|
module.exports = { router, setLogger, setStore };
|
|
954
1098
|
|
|
955
1099
|
// ── dynsec: ACL topic inspection ──────────────────────────────────────────────
|