// Node-RED's settings, written by the mesh from the nodered module. What an assignment may change // is settings.json beside this file (merged key by key); the credentials are the mesh's secrets and // reach Node-RED only through this file. The flows' own credentials stay encrypted in the user // directory under the key Node-RED keeps there (.config.runtime.json), which is data, not this. const fs = require("fs"); const path = require("path"); const crypto = require("crypto"); const ADMIN_PASSWORD = "${secret:admin}"; const API_TOKEN = "${secret:api-token}"; const ADMIN = { username: "admin", permissions: "*" }; const settings = JSON.parse(fs.readFileSync(path.join(__dirname, "settings.json"), "utf8")); // The mesh's keys, not Node-RED's: endpoints lands in every merged file; timeZone is the // assignment's way to set the zone flows schedule and format in; mqtt names the broker nodes the // module's MQTT step keeps pointed at the mesh's broker; topics is what nodered asks the broker for. if (settings.timeZone) process.env.TZ = settings.timeZone; delete settings.timeZone; delete settings.endpoints; delete settings.mqtt; delete settings.topics; function same(a, b) { const x = crypto.createHash("sha256").update(String(a)).digest(); const y = crypto.createHash("sha256").update(String(b)).digest(); return crypto.timingSafeEqual(x, y); } // The admin secret is a password, or, accepted from an existing install, the bcrypt hash its // settings held, so the password people already use keeps working. function passwordMatches(given) { if (/^\$2[aby]\$\d\d\$/.test(ADMIN_PASSWORD)) return require("bcryptjs").compare(String(given), ADMIN_PASSWORD); return Promise.resolve(same(given, ADMIN_PASSWORD)); } module.exports = Object.assign(settings, { uiPort: 1880, adminAuth: { type: "credentials", users: (username) => Promise.resolve(username === ADMIN.username ? ADMIN : null), authenticate: (username, password) => username === ADMIN.username ? passwordMatches(password).then((ok) => (ok ? ADMIN : null)) : Promise.resolve(null), // The module's own tools call the admin API with this bearer token. tokens: (token) => Promise.resolve(same(token, API_TOKEN) ? { username: "mesh", permissions: "*" } : null), }, });