@riddix/hamh 2.1.0-alpha.592 → 2.1.0-alpha.593
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/backend/cli.js
CHANGED
|
@@ -10026,7 +10026,7 @@ function MockCrypto(index = 128, implementation = StandardCrypto) {
|
|
|
10026
10026
|
throw new ImplementationError(`Index for stable crypto must be 0-255`);
|
|
10027
10027
|
}
|
|
10028
10028
|
const crypto8 = new implementation();
|
|
10029
|
-
const { randomBytes:
|
|
10029
|
+
const { randomBytes: randomBytes3, createKeyPair } = crypto8;
|
|
10030
10030
|
Object.defineProperties(crypto8, {
|
|
10031
10031
|
index: {
|
|
10032
10032
|
get() {
|
|
@@ -10038,11 +10038,11 @@ function MockCrypto(index = 128, implementation = StandardCrypto) {
|
|
|
10038
10038
|
},
|
|
10039
10039
|
entropic: {
|
|
10040
10040
|
get() {
|
|
10041
|
-
return crypto8.randomBytes ===
|
|
10041
|
+
return crypto8.randomBytes === randomBytes3;
|
|
10042
10042
|
},
|
|
10043
10043
|
set(entropic) {
|
|
10044
10044
|
if (entropic) {
|
|
10045
|
-
crypto8.randomBytes =
|
|
10045
|
+
crypto8.randomBytes = randomBytes3;
|
|
10046
10046
|
crypto8.createKeyPair = createKeyPair;
|
|
10047
10047
|
} else {
|
|
10048
10048
|
disableEntropy();
|
|
@@ -148035,6 +148035,17 @@ async function extractBackupData(buffer) {
|
|
|
148035
148035
|
const data = JSON.parse(content.toString());
|
|
148036
148036
|
return { backupData: data, zipDirectory: directory };
|
|
148037
148037
|
}
|
|
148038
|
+
function resolveWithin(baseDir, relative) {
|
|
148039
|
+
if (relative.length === 0 || path.isAbsolute(relative)) {
|
|
148040
|
+
return null;
|
|
148041
|
+
}
|
|
148042
|
+
const resolvedBase = path.resolve(baseDir);
|
|
148043
|
+
const resolvedTarget = path.resolve(resolvedBase, relative);
|
|
148044
|
+
if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(resolvedBase + path.sep)) {
|
|
148045
|
+
return null;
|
|
148046
|
+
}
|
|
148047
|
+
return resolvedTarget;
|
|
148048
|
+
}
|
|
148038
148049
|
async function restoreIdentityFiles(zipDirectory, bridgeId, storageLocation) {
|
|
148039
148050
|
const identityPrefix = `identity/${bridgeId}/`;
|
|
148040
148051
|
const identityFiles = zipDirectory.files.filter(
|
|
@@ -148047,7 +148058,12 @@ async function restoreIdentityFiles(zipDirectory, bridgeId, storageLocation) {
|
|
|
148047
148058
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
148048
148059
|
for (const file of identityFiles) {
|
|
148049
148060
|
const relativePath = file.path.substring(identityPrefix.length);
|
|
148050
|
-
const targetPath =
|
|
148061
|
+
const targetPath = resolveWithin(targetDir, relativePath);
|
|
148062
|
+
if (!targetPath) {
|
|
148063
|
+
throw new Error(
|
|
148064
|
+
`Refusing to restore identity file with unsafe path: ${file.path}`
|
|
148065
|
+
);
|
|
148066
|
+
}
|
|
148051
148067
|
const targetDirPath = path.dirname(targetPath);
|
|
148052
148068
|
fs.mkdirSync(targetDirPath, { recursive: true });
|
|
148053
148069
|
const content = await file.buffer();
|
|
@@ -148067,7 +148083,12 @@ async function restoreBridgeIcon(zipDirectory, bridgeId, storageLocation) {
|
|
|
148067
148083
|
fs.mkdirSync(iconsDir, { recursive: true });
|
|
148068
148084
|
for (const file of iconFiles) {
|
|
148069
148085
|
const fileName = file.path.substring(iconPrefix.length);
|
|
148070
|
-
const targetPath =
|
|
148086
|
+
const targetPath = resolveWithin(iconsDir, fileName);
|
|
148087
|
+
if (!targetPath) {
|
|
148088
|
+
throw new Error(
|
|
148089
|
+
`Refusing to restore bridge icon with unsafe path: ${file.path}`
|
|
148090
|
+
);
|
|
148091
|
+
}
|
|
148071
148092
|
const content = await file.buffer();
|
|
148072
148093
|
fs.writeFileSync(targetPath, content);
|
|
148073
148094
|
}
|
|
@@ -151436,16 +151457,25 @@ var WebApi = class extends Service {
|
|
|
151436
151457
|
});
|
|
151437
151458
|
}
|
|
151438
151459
|
createDynamicAuthMiddleware() {
|
|
151460
|
+
const envAuth = this.props.auth;
|
|
151461
|
+
const envMiddleware = envAuth ? basicAuth({
|
|
151462
|
+
users: { [envAuth.username]: envAuth.password },
|
|
151463
|
+
challenge: true,
|
|
151464
|
+
realm: "Home Assistant Matter Hub"
|
|
151465
|
+
}) : void 0;
|
|
151466
|
+
const storageMiddleware = basicAuth({
|
|
151467
|
+
authorizer: (username, password) => this.settingsStorage.verifyAuth(username, password),
|
|
151468
|
+
challenge: true,
|
|
151469
|
+
realm: "Home Assistant Matter Hub"
|
|
151470
|
+
});
|
|
151439
151471
|
return (req, res, next) => {
|
|
151440
|
-
|
|
151441
|
-
|
|
151472
|
+
if (envMiddleware) {
|
|
151473
|
+
return envMiddleware(req, res, next);
|
|
151474
|
+
}
|
|
151475
|
+
if (!this.settingsStorage.auth) {
|
|
151442
151476
|
return next();
|
|
151443
151477
|
}
|
|
151444
|
-
return
|
|
151445
|
-
users: { [auth.username]: auth.password },
|
|
151446
|
-
challenge: true,
|
|
151447
|
-
realm: "Home Assistant Matter Hub"
|
|
151448
|
-
})(req, res, next);
|
|
151478
|
+
return storageMiddleware(req, res, next);
|
|
151449
151479
|
};
|
|
151450
151480
|
}
|
|
151451
151481
|
async start() {
|
|
@@ -151539,39 +151569,43 @@ var BetterLogger = class _BetterLogger extends Logger {
|
|
|
151539
151569
|
}
|
|
151540
151570
|
}
|
|
151541
151571
|
debug(...values4) {
|
|
151542
|
-
|
|
151543
|
-
|
|
151544
|
-
|
|
151545
|
-
|
|
151546
|
-
|
|
151547
|
-
|
|
151572
|
+
if (this._level <= LogLevel.DEBUG) {
|
|
151573
|
+
addLogEntry({
|
|
151574
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
151575
|
+
level: "debug",
|
|
151576
|
+
message: `[${this.loggerName}] ${values4.map((v) => String(v)).join(" ")}`
|
|
151577
|
+
});
|
|
151578
|
+
}
|
|
151548
151579
|
super.debug(...values4);
|
|
151549
151580
|
}
|
|
151550
151581
|
info(...values4) {
|
|
151551
|
-
|
|
151552
|
-
|
|
151553
|
-
|
|
151554
|
-
|
|
151555
|
-
|
|
151556
|
-
|
|
151582
|
+
if (this._level <= LogLevel.INFO) {
|
|
151583
|
+
addLogEntry({
|
|
151584
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
151585
|
+
level: "info",
|
|
151586
|
+
message: `[${this.loggerName}] ${values4.map((v) => String(v)).join(" ")}`
|
|
151587
|
+
});
|
|
151588
|
+
}
|
|
151557
151589
|
super.info(...values4);
|
|
151558
151590
|
}
|
|
151559
151591
|
warn(...values4) {
|
|
151560
|
-
|
|
151561
|
-
|
|
151562
|
-
|
|
151563
|
-
|
|
151564
|
-
|
|
151565
|
-
|
|
151592
|
+
if (this._level <= LogLevel.WARN) {
|
|
151593
|
+
addLogEntry({
|
|
151594
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
151595
|
+
level: "warn",
|
|
151596
|
+
message: `[${this.loggerName}] ${values4.map((v) => String(v)).join(" ")}`
|
|
151597
|
+
});
|
|
151598
|
+
}
|
|
151566
151599
|
super.warn(...values4);
|
|
151567
151600
|
}
|
|
151568
151601
|
error(...values4) {
|
|
151569
|
-
|
|
151570
|
-
|
|
151571
|
-
|
|
151572
|
-
|
|
151573
|
-
|
|
151574
|
-
|
|
151602
|
+
if (this._level <= LogLevel.ERROR) {
|
|
151603
|
+
addLogEntry({
|
|
151604
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
151605
|
+
level: "error",
|
|
151606
|
+
message: `[${this.loggerName}] ${values4.map((v) => String(v)).join(" ")}`
|
|
151607
|
+
});
|
|
151608
|
+
}
|
|
151575
151609
|
super.error(...values4);
|
|
151576
151610
|
}
|
|
151577
151611
|
debugCtx(message, context) {
|
|
@@ -152643,10 +152677,56 @@ function mockDeviceId(entityId) {
|
|
|
152643
152677
|
|
|
152644
152678
|
// src/services/storage/app-settings-storage.ts
|
|
152645
152679
|
init_service();
|
|
152680
|
+
import { randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
152681
|
+
var SCRYPT_KEYLEN = 64;
|
|
152682
|
+
var SCRYPT_COST_N = 16384;
|
|
152683
|
+
var SCRYPT_BLOCK_R = 8;
|
|
152684
|
+
var SCRYPT_PARALLEL_P = 1;
|
|
152685
|
+
var SALT_BYTES = 16;
|
|
152646
152686
|
var DEFAULT_BACKUP_SETTINGS = {
|
|
152647
152687
|
autoBackup: true,
|
|
152648
152688
|
backupRetentionCount: 5
|
|
152649
152689
|
};
|
|
152690
|
+
function hashPassword(plain2) {
|
|
152691
|
+
const salt = randomBytes(SALT_BYTES);
|
|
152692
|
+
const derived = scryptSync(plain2, salt, SCRYPT_KEYLEN, {
|
|
152693
|
+
N: SCRYPT_COST_N,
|
|
152694
|
+
r: SCRYPT_BLOCK_R,
|
|
152695
|
+
p: SCRYPT_PARALLEL_P
|
|
152696
|
+
});
|
|
152697
|
+
return {
|
|
152698
|
+
passwordHash: derived.toString("hex"),
|
|
152699
|
+
passwordSalt: salt.toString("hex")
|
|
152700
|
+
};
|
|
152701
|
+
}
|
|
152702
|
+
function verifyPasswordHash(plain2, passwordHash, passwordSalt) {
|
|
152703
|
+
let expected;
|
|
152704
|
+
let salt;
|
|
152705
|
+
try {
|
|
152706
|
+
expected = Buffer.from(passwordHash, "hex");
|
|
152707
|
+
salt = Buffer.from(passwordSalt, "hex");
|
|
152708
|
+
} catch {
|
|
152709
|
+
return false;
|
|
152710
|
+
}
|
|
152711
|
+
if (expected.length !== SCRYPT_KEYLEN || salt.length === 0) {
|
|
152712
|
+
return false;
|
|
152713
|
+
}
|
|
152714
|
+
const derived = scryptSync(plain2, salt, SCRYPT_KEYLEN, {
|
|
152715
|
+
N: SCRYPT_COST_N,
|
|
152716
|
+
r: SCRYPT_BLOCK_R,
|
|
152717
|
+
p: SCRYPT_PARALLEL_P
|
|
152718
|
+
});
|
|
152719
|
+
return timingSafeEqual(derived, expected);
|
|
152720
|
+
}
|
|
152721
|
+
function constantTimeStringEquals(a, b) {
|
|
152722
|
+
const bufA = Buffer.from(a);
|
|
152723
|
+
const bufB = Buffer.from(b);
|
|
152724
|
+
if (bufA.length !== bufB.length) {
|
|
152725
|
+
timingSafeEqual(bufA, bufA);
|
|
152726
|
+
return false;
|
|
152727
|
+
}
|
|
152728
|
+
return timingSafeEqual(bufA, bufB);
|
|
152729
|
+
}
|
|
152650
152730
|
var AppSettingsStorage = class extends Service {
|
|
152651
152731
|
constructor(appStorage) {
|
|
152652
152732
|
super("AppSettingsStorage");
|
|
@@ -152654,6 +152734,11 @@ var AppSettingsStorage = class extends Service {
|
|
|
152654
152734
|
}
|
|
152655
152735
|
storage;
|
|
152656
152736
|
settings = {};
|
|
152737
|
+
// Caches the last plaintext password that was successfully verified
|
|
152738
|
+
// against the stored hash, keyed by username. This lets repeated basic-auth
|
|
152739
|
+
// calls avoid re-running scrypt on every HTTP request without persisting
|
|
152740
|
+
// the plaintext to disk.
|
|
152741
|
+
verifiedPassword = null;
|
|
152657
152742
|
async initialize() {
|
|
152658
152743
|
this.storage = this.appStorage.createContext("settings");
|
|
152659
152744
|
const stored = await this.storage.get(
|
|
@@ -152663,12 +152748,72 @@ var AppSettingsStorage = class extends Service {
|
|
|
152663
152748
|
this.settings = stored ?? {};
|
|
152664
152749
|
}
|
|
152665
152750
|
get auth() {
|
|
152666
|
-
|
|
152751
|
+
if (!this.settings.auth) {
|
|
152752
|
+
return void 0;
|
|
152753
|
+
}
|
|
152754
|
+
return { username: this.settings.auth.username };
|
|
152667
152755
|
}
|
|
152668
|
-
async setAuth(
|
|
152669
|
-
|
|
152756
|
+
async setAuth(credentials) {
|
|
152757
|
+
if (!credentials) {
|
|
152758
|
+
this.settings.auth = void 0;
|
|
152759
|
+
this.verifiedPassword = null;
|
|
152760
|
+
} else {
|
|
152761
|
+
const { passwordHash, passwordSalt } = hashPassword(credentials.password);
|
|
152762
|
+
this.settings.auth = {
|
|
152763
|
+
username: credentials.username,
|
|
152764
|
+
passwordHash,
|
|
152765
|
+
passwordSalt
|
|
152766
|
+
};
|
|
152767
|
+
this.verifiedPassword = {
|
|
152768
|
+
username: credentials.username,
|
|
152769
|
+
password: credentials.password
|
|
152770
|
+
};
|
|
152771
|
+
}
|
|
152670
152772
|
await this.persist();
|
|
152671
152773
|
}
|
|
152774
|
+
verifyAuth(username, password) {
|
|
152775
|
+
const stored = this.settings.auth;
|
|
152776
|
+
if (!stored) {
|
|
152777
|
+
return false;
|
|
152778
|
+
}
|
|
152779
|
+
if (!constantTimeStringEquals(username, stored.username)) {
|
|
152780
|
+
return false;
|
|
152781
|
+
}
|
|
152782
|
+
if (this.verifiedPassword && this.verifiedPassword.username === stored.username && constantTimeStringEquals(password, this.verifiedPassword.password)) {
|
|
152783
|
+
return true;
|
|
152784
|
+
}
|
|
152785
|
+
if (stored.password !== void 0) {
|
|
152786
|
+
const matches = constantTimeStringEquals(password, stored.password);
|
|
152787
|
+
if (matches) {
|
|
152788
|
+
this.verifiedPassword = { username: stored.username, password };
|
|
152789
|
+
void this.migrateLegacyPlaintext(password);
|
|
152790
|
+
}
|
|
152791
|
+
return matches;
|
|
152792
|
+
}
|
|
152793
|
+
if (stored.passwordHash && stored.passwordSalt) {
|
|
152794
|
+
if (verifyPasswordHash(password, stored.passwordHash, stored.passwordSalt)) {
|
|
152795
|
+
this.verifiedPassword = { username: stored.username, password };
|
|
152796
|
+
return true;
|
|
152797
|
+
}
|
|
152798
|
+
}
|
|
152799
|
+
return false;
|
|
152800
|
+
}
|
|
152801
|
+
async migrateLegacyPlaintext(plain2) {
|
|
152802
|
+
const current = this.settings.auth;
|
|
152803
|
+
if (!current || current.password === void 0) {
|
|
152804
|
+
return;
|
|
152805
|
+
}
|
|
152806
|
+
const { passwordHash, passwordSalt } = hashPassword(plain2);
|
|
152807
|
+
this.settings.auth = {
|
|
152808
|
+
username: current.username,
|
|
152809
|
+
passwordHash,
|
|
152810
|
+
passwordSalt
|
|
152811
|
+
};
|
|
152812
|
+
try {
|
|
152813
|
+
await this.persist();
|
|
152814
|
+
} catch {
|
|
152815
|
+
}
|
|
152816
|
+
}
|
|
152672
152817
|
get backupSettings() {
|
|
152673
152818
|
return { ...DEFAULT_BACKUP_SETTINGS, ...this.settings.backup };
|
|
152674
152819
|
}
|
|
@@ -152977,7 +153122,7 @@ var EntityMappingStorage = class extends Service {
|
|
|
152977
153122
|
|
|
152978
153123
|
// src/services/storage/lock-credential-storage.ts
|
|
152979
153124
|
init_service();
|
|
152980
|
-
import { pbkdf2Sync, randomBytes } from "node:crypto";
|
|
153125
|
+
import { pbkdf2Sync, randomBytes as randomBytes2 } from "node:crypto";
|
|
152981
153126
|
var CURRENT_VERSION2 = 2;
|
|
152982
153127
|
var HASH_ITERATIONS = 1e5;
|
|
152983
153128
|
var HASH_KEY_LENGTH = 64;
|
|
@@ -153014,7 +153159,7 @@ var LockCredentialStorage = class extends Service {
|
|
|
153014
153159
|
async migrate(data) {
|
|
153015
153160
|
if (data.version === 1) {
|
|
153016
153161
|
for (const legacyCredential of data.credentials) {
|
|
153017
|
-
const salt =
|
|
153162
|
+
const salt = randomBytes2(SALT_LENGTH).toString("hex");
|
|
153018
153163
|
const hash2 = this.hashPin(legacyCredential.pinCode, salt);
|
|
153019
153164
|
const credential = {
|
|
153020
153165
|
entityId: legacyCredential.entityId,
|
|
@@ -153080,7 +153225,7 @@ var LockCredentialStorage = class extends Service {
|
|
|
153080
153225
|
async setCredential(request) {
|
|
153081
153226
|
const now = Date.now();
|
|
153082
153227
|
const existing = this.credentials.get(request.entityId);
|
|
153083
|
-
const salt =
|
|
153228
|
+
const salt = randomBytes2(SALT_LENGTH).toString("hex");
|
|
153084
153229
|
const hash2 = this.hashPin(request.pinCode, salt);
|
|
153085
153230
|
const credential = {
|
|
153086
153231
|
entityId: request.entityId,
|