Skip to content

Commit 8024519

Browse files
authored
Merge pull request #223 from mStirner/dev
Working towards v2
2 parents 0991d83 + 24a4c3d commit 8024519

File tree

20 files changed

+752
-111
lines changed

20 files changed

+752
-111
lines changed

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"ignorePatterns": [
1111
"node_modules",
1212
"logs",
13-
"plugins/*"
13+
"plugins/*",
14+
"dist"
1415
],
1516
"extends": [
1617
"eslint:recommended"

.github/workflows/node-js.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
strategy:
2828
matrix:
2929
os: [ubuntu-latest]
30-
node-version: [16.x, 17.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
30+
node-version: [16.x, 17.x, 18.x] # See supported Node.js release schedule at https://nodejs.org/en/about/releases/
3131
mongodb-version: ["4.2", "4.4", "5.0"]
3232

3333
steps:

Gruntfile.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ module.exports = function (grunt) {
4444
run: {
4545
install: {
4646
options: {
47-
cwd: "./dist"
47+
cwd: path.join(process.cwd(), "dist")
4848
},
4949
cmd: "npm",
5050
args: [
@@ -64,6 +64,12 @@ module.exports = function (grunt) {
6464
},
6565
folder: {
6666
exec: "mkdir ./dist/logs && mkdir ./dist/plugins"
67+
},
68+
"scripts-mock": {
69+
exec: "mkdir ./dist/scripts && echo 'exit 0' > ./dist/scripts/post-install.sh && chmod +x ./dist/scripts/post-install.sh"
70+
},
71+
"scripts-cleanup": {
72+
exec: "rm ./dist/scripts/post-install.sh && rmdir ./dist/scripts"
6773
}
6874
},
6975
compress: {
@@ -99,7 +105,9 @@ module.exports = function (grunt) {
99105
// install npm dependencies
100106
grunt.registerTask("install", [
101107
"env:prod",
108+
"run:scripts-mock",
102109
"run:install",
110+
"run:scripts-cleanup"
103111
]);
104112

105113
grunt.registerTask("bundle", [

components/endpoints/class.command.js

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@ const { interfaces } = require("../../system/shared.js");
1919
* @property {String} alias Machine friendly name, e.g.: `POWER_ON`
2020
* @property {String} [identifier=null] Simple/custom identifiert for custom command handler
2121
* @property {String} payload The payload to send over the device interface
22-
* @property {String} [description=""] Command description, displayed on the frontend
22+
* @property {String} [description=null] Command description, displayed on the frontend
2323
* @property {Array} params Possible parameter for the command
2424
* @property {String} params[].key Custom key
25-
* @property {Any} params[].value Value to set
26-
* @property {String} params[].default Default thing if nothing is send from client
27-
* @property {String} params[].min Min value if param type is a number
28-
* @property {String} params[].max Max value if param type is a number
25+
* @property {String} params[].type Type of value: "string", "number" or "boolean"
26+
* @property {String|Number|Boolean} params[].value Value to set
27+
* @property {Number} [params[].min=0] Min value if param type is a number (`type=number`)
28+
* @property {Number} [params[].max=100] Max value if param type is a number (`type=number`)
2929
*
3030
* @example
3131
* ```json
@@ -223,13 +223,29 @@ module.exports = class Command {
223223
identifier: Joi.string().allow(null).default(null), // NOTE: move to endpoint schema? // Thing api provides you, like light id or some custom thing for you
224224
payload: Joi.string().allow(null).default(null),
225225
description: Joi.string().allow(null).default(null),
226-
params: Joi.array().items({
227-
key: Joi.string().required(),
228-
value: Joi.any(),
229-
default: Joi.string(),
230-
min: Joi.number(),
231-
max: Joi.number()
232-
})
226+
params: Joi.array().items(Joi.object({
227+
type: Joi.string().valid("number", "string", "boolean").required(),
228+
key: Joi.string().required()
229+
}).when(".type", {
230+
switch: [{
231+
is: "number",
232+
then: Joi.object({
233+
value: Joi.number().default(null).allow(null),
234+
min: Joi.number().default(0),
235+
max: Joi.number().default(100)
236+
})
237+
}, {
238+
is: "string",
239+
then: Joi.object({
240+
value: Joi.string().default(null).allow(null)
241+
})
242+
}, {
243+
is: "boolean",
244+
then: Joi.object({
245+
value: Joi.boolean().default(null).allow(null)
246+
})
247+
}]
248+
}))
233249
});
234250
}
235251

components/plugins/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class C_PLUGINS extends COMPONENT {
4444
//runlevel: Joi.number().min(0).max(2).default(0),
4545
autostart: Joi.boolean().default(true),
4646
enabled: Joi.boolean().default(true),
47-
intents: Joi.array().items("devices", "endpoints", "plugins", "rooms", "ssdp", "store", "users", "vault",).required()
47+
intents: Joi.array().items("devices", "endpoints", "plugins", "rooms", "ssdp", "store", "users", "vault").required()
4848
}, module);
4949

5050
this.hooks.post("add", (data, next) => {

components/vault/class.secret.js

Lines changed: 11 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1-
const crypto = require("crypto");
21
const mongodb = require("mongodb");
32
const Joi = require("joi");
43

4+
const encrypt = require("./encrypt.js");
5+
const decrypt = require("./decrypt.js");
6+
57
// https://stackoverflow.com/a/53573115/5781499
68
// https://www.tutorialspoint.com/encrypt-and-decrypt-data-in-nodejs
79
// https://stackoverflow.com/q/70093261/5781499
@@ -31,6 +33,11 @@ class Secret {
3133
Object.defineProperty(this, "value", {
3234
set(value) {
3335

36+
// check if value is allready encrypted
37+
if (value?.split(":")?.length === 1) {
38+
value = encrypt(value);
39+
}
40+
3441
// ignore usless set
3542
// related to #219
3643
if (value == obj.value) {
@@ -45,6 +52,7 @@ class Secret {
4552
get() {
4653
return obj.value;
4754
},
55+
// NOTE: Make value field not enumarble?
4856
configurable: false
4957
});
5058

@@ -80,20 +88,7 @@ class Secret {
8088
* @returns {String} Ecnrypted string
8189
*/
8290
encrypt(text) {
83-
84-
let iv = crypto.randomBytes(Number(process.env.VAULT_IV_BYTE_LEN));
85-
let salt = crypto.randomBytes(Number(process.env.VAULT_SALT_BYTE_LEN));
86-
let key = crypto.scryptSync(process.env.VAULT_MASTER_PASSWORD, salt, Number(process.env.VAULT_KEY_BYTE_LEN));
87-
88-
let cipher = crypto.createCipheriv(process.env.VAULT_BLOCK_CIPHER, key, iv, {
89-
authTagLength: Number(process.env.VAULT_AUTH_TAG_BYTE_LEN)
90-
});
91-
92-
let encrypted = cipher.update(text, "utf8", "hex");
93-
encrypted += cipher.final("hex");
94-
95-
return this.value = `${iv.toString("hex")}:${salt.toString("hex")}:${encrypted}`;
96-
91+
this.value = encrypt(text);
9792
}
9893

9994

@@ -104,21 +99,7 @@ class Secret {
10499
* @returns {String} Decrypted string
105100
*/
106101
decrypt() {
107-
108-
let [ivs, salts, data] = this.value.split(":");
109-
let iv = Buffer.from(ivs, "hex");
110-
let salt = Buffer.from(salts, "hex");
111-
let key = crypto.scryptSync(process.env.VAULT_MASTER_PASSWORD, salt, Number(process.env.VAULT_KEY_BYTE_LEN));
112-
113-
let decipher = crypto.createDecipheriv(process.env.VAULT_BLOCK_CIPHER, key, iv, {
114-
authTagLength: Number(process.env.VAULT_AUTH_TAG_BYTE_LEN)
115-
});
116-
117-
let decrypted = decipher.update(data, "hex", "utf8");
118-
decrypted += decipher.final("utf8");
119-
120-
return decrypted.toString();
121-
102+
return decrypt(this.value);
122103
}
123104

124105
}

components/vault/decrypt.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const crypto = require("crypto");
2+
3+
/**
4+
* @function decrypt
5+
* Interal function that handels the decryption
6+
*
7+
* @internal
8+
*
9+
* @param {String} value Encrypted string
10+
* @returns {String} Decrypted string
11+
*/
12+
function decrypt(value) {
13+
14+
let [ivs, salts, data] = value.split(":");
15+
let iv = Buffer.from(ivs, "hex");
16+
let salt = Buffer.from(salts, "hex");
17+
let key = crypto.scryptSync(process.env.VAULT_MASTER_PASSWORD, salt, Number(process.env.VAULT_KEY_BYTE_LEN));
18+
19+
let decipher = crypto.createDecipheriv(process.env.VAULT_BLOCK_CIPHER, key, iv, {
20+
authTagLength: Number(process.env.VAULT_AUTH_TAG_BYTE_LEN)
21+
});
22+
23+
let decrypted = decipher.update(data, "hex", "utf8");
24+
decrypted += decipher.final("utf8");
25+
26+
return decrypted.toString();
27+
28+
}
29+
30+
module.exports = decrypt;

components/vault/encrypt.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
const crypto = require("crypto");
2+
3+
/**
4+
* @function encrypt
5+
* Interal function that handels the encrpytion
6+
*
7+
* @internal
8+
*
9+
* @param {String} value Vanilla string
10+
* @returns {String} Encrypted string
11+
*/
12+
function encrypt(text) {
13+
14+
let iv = crypto.randomBytes(Number(process.env.VAULT_IV_BYTE_LEN));
15+
let salt = crypto.randomBytes(Number(process.env.VAULT_SALT_BYTE_LEN));
16+
let key = crypto.scryptSync(process.env.VAULT_MASTER_PASSWORD, salt, Number(process.env.VAULT_KEY_BYTE_LEN));
17+
18+
let cipher = crypto.createCipheriv(process.env.VAULT_BLOCK_CIPHER, key, iv, {
19+
authTagLength: Number(process.env.VAULT_AUTH_TAG_BYTE_LEN)
20+
});
21+
22+
let encrypted = cipher.update(text, "utf8", "hex");
23+
encrypted += cipher.final("hex");
24+
25+
return `${iv.toString("hex")}:${salt.toString("hex")}:${encrypted}`;
26+
27+
}
28+
29+
module.exports = encrypt;

components/vault/index.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const COMPONENT = require("../../system/component/class.component.js");
88
const Vault = require("./class.vault.js");
99
const Secret = require("./class.secret.js");
1010

11+
const encrypt = require("./encrypt.js");
12+
1113
/**
1214
* @description
1315
* Vault component to handle secrets, credentials & tokens.<br />
@@ -42,10 +44,54 @@ class C_VAULT extends COMPONENT {
4244
secrets: Joi.array().items(Secret.schema()).default([])
4345
}, module);
4446

47+
this.hooks.pre("add", (data, next) => {
48+
try {
49+
50+
if (data?.secrets) {
51+
data.secrets = data.secrets.map((secret) => {
52+
53+
if (secret.value) {
54+
secret.value = encrypt(secret.value);
55+
}
56+
57+
return secret;
58+
59+
});
60+
}
61+
62+
next(null);
63+
64+
} catch (err) {
65+
next(err);
66+
}
67+
});
68+
4569
this.hooks.post("add", (data, next) => {
4670
next(null, new Vault(data, this));
4771
});
4872

73+
/*
74+
// investigation of #208
75+
this.hooks.post("update", (data, next) => {
76+
77+
console.log("update post:", data);
78+
79+
let valid = data.secrets.every((secret) => {
80+
return secret instanceof Secret;
81+
});
82+
83+
console.log("Secrets instances valid:", valid, data.secrets)
84+
85+
if (!valid) {
86+
//_merge(item, new Vault(data, this));
87+
//Object.assign(data, new Vault(data, this));
88+
}
89+
90+
next(null);
91+
92+
});
93+
*/
94+
4995
}
5096

5197
}

system/component/class.component.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ module.exports = class COMPONENT extends COMMON {
162162

163163
// trigger update event
164164
// TODO trigger update event, so changes can be detect via websockets /events API?
165-
this.events.emit("update", [target]);
165+
this.events.emit("update", target);
166166

167167
} else if (event.operationType === "update") {
168168

@@ -211,7 +211,7 @@ module.exports = class COMPONENT extends COMMON {
211211

212212
// trigger update event
213213
// TODO trigger update event, so changes can be detect via websockets /events API?
214-
this.events.emit("update", [target]);
214+
this.events.emit("update", target);
215215

216216
});
217217

0 commit comments

Comments
 (0)