mirror of
https://github.com/MichMich/MagicMirror.git
synced 2026-04-24 06:47:07 +00:00
## Loading `config.js` ### Previously Loaded on server-side in `app.js` and in the browser by including `config.js` in `index.html`. The web server has an endpoint `/config` providing the content of server loaded `config.js`. ### Now Loaded only on server-side in `app.js`. The browser loads the content using the web server endpoint `/config`. So the server has control what to provide to the clients. Loading the `config.js` was moved to `Utils.js` so that `check_config.js` can use the same functions. ## Using environment variables in `config.js` ### Previously Environment variables were not allowed in `config.js`. The workaround was to create a `config.js.template` with curly braced bash variables allowed. While starting the app the `config.js.template` was converted via `envsub` into a `config.js`. ### Now Curly braced bash variables are allowed in `config.js`. Because only the server loads `config.js` he can substitute the variables while loading. ## Secrets in MagicMirror² To be honest, this is a mess. ### Previously All content defined in the `config` directory was reachable from the browser. Everyone with access to the site could see all stuff defined in the configuration e.g. using the url http://ip:8080/config. This included api keys and other secrets. So sharing a MagicMirror² url to others or running MagicMirror² without authentication as public website was not possible. ### Now With this PR we add (beta) functionality to protect sensitive data. This is only possible for modules running with a `node_helper`. For modules running in the browser only (e.g. default `weather` module), there is no way to hide data (per construction). This does not mean, that every module with `node_helper` is safe, e.g. the default `calendar` module is not safe because it uses the calendar url's as sort of id and sends them to the client. For adding more security you have to set `hideConfigSecrets: true` in `config.js`. With this: - `config/config.env` is not deliverd to the browser - the contents of environment variables beginning with `SECRET_` are not published to the clients This is a first step to protect sensitive data and you can at least protect some secrets.
142 lines
3.7 KiB
JavaScript
142 lines
3.7 KiB
JavaScript
const express = require("express");
|
|
const Log = require("logger");
|
|
const Class = require("./class");
|
|
|
|
const NodeHelper = Class.extend({
|
|
init () {
|
|
Log.log("Initializing new module helper ...");
|
|
},
|
|
|
|
loaded () {
|
|
Log.log(`Module helper loaded: ${this.name}`);
|
|
},
|
|
|
|
start () {
|
|
Log.log(`Starting module helper: ${this.name}`);
|
|
},
|
|
|
|
/**
|
|
* Called when the MagicMirror² server receives a `SIGINT`
|
|
* Close any open connections, stop any sub-processes and
|
|
* gracefully exit the module.
|
|
*/
|
|
stop () {
|
|
Log.log(`Stopping module helper: ${this.name}`);
|
|
},
|
|
|
|
/**
|
|
* This method is called when a socket notification arrives.
|
|
* @param {string} notification The identifier of the notification.
|
|
* @param {object} payload The payload of the notification.
|
|
*/
|
|
socketNotificationReceived (notification, payload) {
|
|
Log.log(`${this.name} received a socket notification: ${notification} - Payload: ${payload}`);
|
|
},
|
|
|
|
/**
|
|
* Set the module name.
|
|
* @param {string} name Module name.
|
|
*/
|
|
setName (name) {
|
|
this.name = name;
|
|
},
|
|
|
|
/**
|
|
* Set the module path.
|
|
* @param {string} path Module path.
|
|
*/
|
|
setPath (path) {
|
|
this.path = path;
|
|
},
|
|
|
|
/*
|
|
* sendSocketNotification(notification, payload)
|
|
* Send a socket notification to the node helper.
|
|
*
|
|
* argument notification string - The identifier of the notification.
|
|
* argument payload mixed - The payload of the notification.
|
|
*/
|
|
sendSocketNotification (notification, payload) {
|
|
this.io.of(this.name).emit(notification, payload);
|
|
},
|
|
|
|
/*
|
|
* setExpressApp(app)
|
|
* Sets the express app object for this module.
|
|
* This allows you to host files from the created webserver.
|
|
*
|
|
* argument app Express app - The Express app object.
|
|
*/
|
|
setExpressApp (app) {
|
|
this.expressApp = app;
|
|
|
|
app.use(`/${this.name}`, express.static(`${this.path}/public`));
|
|
},
|
|
|
|
/*
|
|
* setSocketIO(io)
|
|
* Sets the socket io object for this module.
|
|
* Binds message receiver.
|
|
*
|
|
* argument io Socket.io - The Socket io object.
|
|
*/
|
|
setSocketIO (io) {
|
|
this.io = io;
|
|
|
|
Log.log(`Connecting socket for: ${this.name}`);
|
|
|
|
io.of(this.name).on("connection", (socket) => {
|
|
// register catch all.
|
|
socket.onAny((notification, payload) => {
|
|
if (config.hideConfigSecrets && payload && typeof payload === "object") {
|
|
try {
|
|
const payloadStr = JSON.stringify(payload).replaceAll(/\*\*(SECRET_.*)\*\*/g, (match, group) => {
|
|
return process.env[group];
|
|
});
|
|
this.socketNotificationReceived(notification, JSON.parse(payloadStr));
|
|
} catch (e) {
|
|
Log.error("Error substituting variables in payload: ", e);
|
|
this.socketNotificationReceived(notification, payload);
|
|
}
|
|
} else {
|
|
this.socketNotificationReceived(notification, payload);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
});
|
|
|
|
NodeHelper.checkFetchStatus = function (response) {
|
|
// response.status >= 200 && response.status < 300
|
|
if (response.ok) {
|
|
return response;
|
|
} else {
|
|
throw Error(response.statusText);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Look at the specified error and return an appropriate error type, that
|
|
* can be translated to a detailed error message
|
|
* @param {Error} error the error from fetching something
|
|
* @returns {string} the string of the detailed error message in the translations
|
|
*/
|
|
NodeHelper.checkFetchError = function (error) {
|
|
let error_type = "MODULE_ERROR_UNSPECIFIED";
|
|
if (error.code === "EAI_AGAIN") {
|
|
error_type = "MODULE_ERROR_NO_CONNECTION";
|
|
} else {
|
|
const message = typeof error.message === "string" ? error.message.toLowerCase() : "";
|
|
if (message.includes("unauthorized") || message.includes("http 401") || message.includes("http 403")) {
|
|
error_type = "MODULE_ERROR_UNAUTHORIZED";
|
|
}
|
|
}
|
|
return error_type;
|
|
};
|
|
|
|
NodeHelper.create = function (moduleDefinition) {
|
|
return NodeHelper.extend(moduleDefinition);
|
|
};
|
|
|
|
module.exports = NodeHelper;
|