From 2a604edb9eca98e1029f7de056133585c15a5a82 Mon Sep 17 00:00:00 2001 From: emad-salah Date: Mon, 13 Jan 2020 15:24:35 +0100 Subject: [PATCH 1/5] [WIP] End-to-End encryption store completed --- src/routes.js | 20 ++++++++++--- src/server.js | 19 +++++++++---- utils/encryptionStore.js | 61 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 utils/encryptionStore.js diff --git a/src/routes.js b/src/routes.js index 143492ab..b7021538 100644 --- a/src/routes.js +++ b/src/routes.js @@ -5,9 +5,10 @@ */ "use strict"; -const Http = require("axios"); +const Axios = require("axios"); const Crypto = require("crypto"); const logger = require("winston"); +const httpsAgent = require("https"); const responseTime = require("response-time"); const getListPage = require("../utils/paginate"); const auth = require("../services/auth/auth"); @@ -19,12 +20,18 @@ const { unprotectedRoutes } = require("../utils/protectedRoutes"); const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10; // module.exports = (app) => { -module.exports = ( +module.exports = async ( app, config, mySocketsEvents, - { serverPort } + { serverPort, CA, CA_KEY, usetls } ) => { + const Http = Axios.create({ + httpsAgent: new httpsAgent.Agent({ + ca: await FS.readFile(CA) + }) + }) + const sanitizeLNDError = (message = "") => message.toLowerCase().includes("unknown") ? message @@ -81,7 +88,7 @@ module.exports = ( const serviceStatus = await getAvailableService(); const LNDStatus = serviceStatus; try { - const APIHealth = await Http.get(`http://localhost:${serverPort}/ping`); + const APIHealth = await Http.get(`${usetls ? 'https' : 'http'}://localhost:${serverPort}/ping`); const APIStatus = { message: APIHealth.data, responseTime: APIHealth.headers["x-response-time"], @@ -92,6 +99,7 @@ module.exports = ( APIStatus }; } catch (err) { + console.error(err); const APIStatus = { message: err.response.data, responseTime: err.response.headers["x-response-time"], @@ -259,6 +267,10 @@ module.exports = ( res.json({ msg: "OK" }); }); + app.post("/api/security/exchangeKeys", (req, res) => { + + }) + app.get("/api/lnd/wallet/status", async (req, res) => { try { const walletStatus = await walletExists(); diff --git a/src/server.js b/src/server.js index b52f0210..3743f2cc 100644 --- a/src/server.js +++ b/src/server.js @@ -125,12 +125,15 @@ const server = program => { .send({ status: 500, errorMessage: "internal error" }); }); + const CA = LightningServices.servicesConfig.lndCertPath + const CA_KEY = CA.replace("cert", "key") + const createServer = async () => { try { - if (program.usetls) { + if (LightningServices.servicesConfig.lndCertPath && program.usetls) { const [key, cert] = await Promise.all([ - FS.readFile(program.usetls + "/key.pem"), - FS.readFile(program.usetls + "/cert.pem") + FS.readFile(CA_KEY), + FS.readFile(CA) ]); const httpsServer = Https.createServer({ key, cert }, app); @@ -141,7 +144,10 @@ const server = program => { return httpServer; } catch (err) { logger.error(err.message); - throw err; + logger.error("An error has occurred while finding an LND cert to use to open an HTTPS server") + logger.warn("Falling back to opening an HTTP server...") + const httpServer = Http.Server(app); + return httpServer } }; @@ -164,7 +170,10 @@ const server = program => { Sockets, { serverHost: module.serverHost, - serverPort: module.serverPort + serverPort: module.serverPort, + usetls: program.usetls, + CA, + CA_KEY } ); diff --git a/utils/encryptionStore.js b/utils/encryptionStore.js new file mode 100644 index 00000000..3e5c75f4 --- /dev/null +++ b/utils/encryptionStore.js @@ -0,0 +1,61 @@ +/** + * @prettier + */ +const Crypto = require('crypto') +const { Buffer } = require('buffer') +const APIKeyPair = new Map() +const authorizedDevices = new Map() + +module.exports = { + encrypt: ({ deviceId, message }) => { + if (!authorizedDevices.has(deviceId)) { + throw { field: 'deviceId', message: 'Unknown Device ID' } + } + + const devicePublicKey = authorizedDevices.get(deviceId) + const data = Buffer.from(message) + const encryptedData = Crypto.publicEncrypt(devicePublicKey, data) + return encryptedData.toString('base64') + }, + decrypt: ({ deviceId, message }) => { + if (!authorizedDevices.has(deviceId)) { + throw { field: 'deviceId', message: 'Unknown Device ID' } + } + + const data = Buffer.from(message, 'base64') + const encryptedData = Crypto.privateDecrypt(APIKeyPair.private, data) + return encryptedData.toString('base64') + }, + authorizeDevice: ({ deviceId, publicKey }) => + new Promise((resolve, reject) => { + if (authorizedDevices.has(deviceId)) { + const error = { success: false, message: 'Device already exists' } + reject(error) + return error + } + + authorizedDevices.set(deviceId, publicKey) + + Crypto.generateKeyPair( + 'rsa', + { + modulusLength: 4096 + }, + (err, publicKey, privateKey) => { + if (err) { + reject({ field: 'APIKeyPair', errorMessage: err }) + return err + } + + APIKeyPair.set(deviceId, { + publicKey, + privateKey + }) + resolve({ success: true }) + } + ) + }), + unAuthorizeDevice: ({ deviceId }) => { + authorizedDevices.delete(deviceId) + } +} From 4126fd503a0d4a7974b901a8eef8536a37cb7a01 Mon Sep 17 00:00:00 2001 From: emad-salah Date: Mon, 20 Jan 2020 15:19:31 +0100 Subject: [PATCH 2/5] End to end encryption completed through HTTP! --- .prettierrc | 2 +- src/routes.js | 221 +++++++++++++++++++++++++------------ src/server.js | 227 +++++++++++++++++++++++---------------- utils/encryptionStore.js | 108 ++++++++++++++++--- utils/protectedRoutes.js | 6 +- yarn.lock | 9 +- 6 files changed, 389 insertions(+), 184 deletions(-) diff --git a/.prettierrc b/.prettierrc index adbf2efe..b6283b83 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,5 +2,5 @@ "requirePragma": true, "semi": false, "singleQuote": true, - "endOfLine": "lf" + "endOfLine": "auto" } diff --git a/src/routes.js b/src/routes.js index b7021538..f0db22d6 100644 --- a/src/routes.js +++ b/src/routes.js @@ -10,14 +10,17 @@ const Crypto = require("crypto"); const logger = require("winston"); const httpsAgent = require("https"); const responseTime = require("response-time"); +const uuid = require("uuid/v4"); const getListPage = require("../utils/paginate"); const auth = require("../services/auth/auth"); const FS = require("../utils/fs"); +const Encryption = require("../utils/encryptionStore"); const LightningServices = require("../utils/lightningServices"); const GunDB = require("../services/gunDB/Mediator"); -const { unprotectedRoutes } = require("../utils/protectedRoutes"); +const { unprotectedRoutes, nonEncryptedRoutes } = require("../utils/protectedRoutes"); const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10; +const SESSION_ID = uuid(); // module.exports = (app) => { module.exports = async ( @@ -116,7 +119,7 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { if (err) { - res.send({ + res.json({ errorMessage: sanitizeLNDError(err.message) }); } else { @@ -124,7 +127,7 @@ module.exports = async ( } } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } }; @@ -192,6 +195,53 @@ module.exports = async ( } }; + app.use((req, res, next) => { + res.setHeader("x-session-id", SESSION_ID) + next() + }) + + app.use((req, res, next) => { + const deviceId = req.headers["x-shockwallet-device-id"]; + try { + if (nonEncryptedRoutes.includes(req.path)) { + return next(); + } + + if (!deviceId) { + const error = { + field: "deviceId", + message: "Please specify a device ID" + }; + console.error(error) + return res.status(401).json(error); + } + + if (!Encryption.isAuthorizedDevice({ deviceId })) { + const error = { + field: "deviceId", + message: "Please specify a device ID" + }; + console.error("Unknown Device", error) + return res.status(401).json(error); + } + + console.log("Body:", req.body) + console.log("Decrypt params:", { deviceId, message: req.body.encryptionKey }) + const decryptedKey = Encryption.decryptKey({ deviceId, message: req.body.encryptionKey }); + console.log("decryptedKey", decryptedKey) + const decryptedMessage = Encryption.decryptMessage({ message: req.body.data, key: decryptedKey, iv: req.body.iv }) + req.body = JSON.parse(decryptedMessage); + return next(); + } catch (err) { + console.error(err); + return res + .status(401) + .json( + err + ); + } + }) + app.use(async (req, res, next) => { try { console.log("Route:", req.path) @@ -247,7 +297,7 @@ module.exports = async ( */ app.get("/health", async (req, res) => { const health = await checkHealth(); - res.send(health); + res.json(health); }); /** @@ -255,11 +305,11 @@ module.exports = async ( */ app.get("/healthz", async (req, res) => { const health = await checkHealth(); - res.send(health); + res.json(health); }); app.get("/ping", (req, res) => { - res.send("OK"); + res.json({ message: "OK" }); }); app.post("/api/mobile/error", (req, res) => { @@ -267,8 +317,36 @@ module.exports = async ( res.json({ msg: "OK" }); }); - app.post("/api/security/exchangeKeys", (req, res) => { + app.post("/api/security/exchangeKeys", async (req, res) => { + try { + const { publicKey, deviceId } = req.body; + + if (!publicKey || publicKey.length < 600) { + return res.status(400).json({ + field: 'publicKey', + message: "Please provide a valid public key" + }) + } + if (!deviceId || + !/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/iu + .test(deviceId)) { + return res.status(400).json({ + field: 'deviceId', + message: "Please provide a valid device ID" + }) + } + + const authorizedDevice = await Encryption.authorizeDevice({ deviceId, publicKey }) + console.log(authorizedDevice) + return res.status(200).json(authorizedDevice) + } catch (err) { + console.error(err) + return res.status(401).json({ + field: 'unknown', + message: err + }) + } }) app.get("/api/lnd/wallet/status", async (req, res) => { @@ -292,6 +370,7 @@ module.exports = async ( app.post("/api/lnd/auth", async (req, res) => { try { + console.log("/api/lnd/auth Body:", req.body) const health = await checkHealth(); const walletInitialized = await walletExists(); // If we're connected to lnd, unlock the wallet using the password supplied @@ -337,7 +416,7 @@ module.exports = async ( } res.status(500); - res.send({ + res.json({ field: "health", errorMessage: sanitizeLNDError(health.LNDStatus.message), success: false @@ -346,7 +425,7 @@ module.exports = async ( } catch (err) { logger.debug("Unlock Error:", err); res.status(400); - res.send({ field: "user", errorMessage: sanitizeLNDError(err.message), success: false }); + res.json({ field: "user", errorMessage: sanitizeLNDError(err.message), success: false }); return err; } }); @@ -368,10 +447,10 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { res.status(400); - res.send({ field: "WalletUnlocker", errorMessage: unlockErr.message }); + res.json({ field: "WalletUnlocker", errorMessage: unlockErr.message }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } else { await recreateLnServices(); @@ -435,12 +514,12 @@ module.exports = async ( const message = genSeedErr.details; return res .status(400) - .send({ field: "GenSeed", errorMessage: message, success: false }); + .json({ field: "GenSeed", errorMessage: message, success: false }); } return res .status(500) - .send({ field: "health", errorMessage: "LND is down", success: false }); + .json({ field: "health", errorMessage: "LND is down", success: false }); } logger.debug("GenSeed:", genSeedResponse); @@ -613,13 +692,13 @@ module.exports = async ( logger.error("GetInfo Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "getInfo", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.info("GetInfo:", response); @@ -644,13 +723,13 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { res.status(400); - res.send({ + res.json({ field: "getNodeInfo", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("GetNodeInfo:", response); @@ -666,13 +745,13 @@ module.exports = async ( logger.debug("GetNetworkInfo Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "getNodeInfo", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("GetNetworkInfo:", response); @@ -688,13 +767,13 @@ module.exports = async ( logger.debug("ListPeers Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "listPeers", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("ListPeers:", response); @@ -710,13 +789,13 @@ module.exports = async ( logger.debug("NewAddress Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "newAddress", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("NewAddress:", response); @@ -731,13 +810,13 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { res.status(403); - return res.send({ + return res.json({ field: "limituser", errorMessage: "User limited" }); } res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } const connectRequest = { addr: { pubkey: req.body.pubkey, host: req.body.host }, @@ -747,7 +826,7 @@ module.exports = async ( lightning.connectPeer(connectRequest, (err, response) => { if (err) { logger.debug("ConnectPeer Error:", err); - res.status(500).send({ field: "connectPeer", errorMessage: sanitizeLNDError(err.message) }); + res.status(500).json({ field: "connectPeer", errorMessage: sanitizeLNDError(err.message) }); } else { logger.debug("ConnectPeer:", response); res.json(response); @@ -762,20 +841,20 @@ module.exports = async ( const health = await checkHealth(); if (health.LNDStatus.success) { res.status(403); - return res.send({ + return res.json({ field: "limituser", errorMessage: "User limited" }); } res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } const disconnectRequest = { pub_key: req.body.pubkey }; logger.debug("DisconnectPeer Request:", disconnectRequest); lightning.disconnectPeer(disconnectRequest, (err, response) => { if (err) { logger.debug("DisconnectPeer Error:", err); - res.status(400).send({ field: "disconnectPeer", errorMessage: sanitizeLNDError(err.message) }); + res.status(400).json({ field: "disconnectPeer", errorMessage: sanitizeLNDError(err.message) }); } else { logger.debug("DisconnectPeer:", response); res.json(response); @@ -791,13 +870,13 @@ module.exports = async ( logger.debug("ListChannels Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "listChannels", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("ListChannels:", response); @@ -813,13 +892,13 @@ module.exports = async ( logger.debug("PendingChannels Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "pendingChannels", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("PendingChannels:", response); @@ -906,10 +985,10 @@ module.exports = async ( logger.debug("ListInvoices Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ errorMessage: sanitizeLNDError(err.message), success: false }); + res.status(400).json({ errorMessage: sanitizeLNDError(err.message), success: false }); } else { res.status(500); - res.send({ errorMessage: health.LNDStatus.message, success: false }); + res.json({ errorMessage: health.LNDStatus.message, success: false }); } } else { // logger.debug("ListInvoices:", response); @@ -932,13 +1011,13 @@ module.exports = async ( logger.debug("ForwardingHistory Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "forwardingHistory", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("ForwardingHistory:", response); @@ -954,13 +1033,13 @@ module.exports = async ( logger.debug("WalletBalance Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "walletBalance", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("WalletBalance:", response); @@ -976,13 +1055,13 @@ module.exports = async ( if (err) { logger.debug("WalletBalance Error:", err); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "walletBalance", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send(health.LNDStatus); + res.json(health.LNDStatus); } return err; } @@ -991,13 +1070,13 @@ module.exports = async ( if (err) { logger.debug("ChannelBalance Error:", err); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "channelBalance", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send(health.LNDStatus); + res.json(health.LNDStatus); } return err; } @@ -1020,11 +1099,11 @@ module.exports = async ( logger.debug("DecodePayReq Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(500).send({ + res.status(500).json({ errorMessage: sanitizeLNDError(err.message) }); } else { - res.status(500).send({ errorMessage: "LND is down" }); + res.status(500).json({ errorMessage: "LND is down" }); } } else { logger.info("DecodePayReq:", paymentRequest); @@ -1042,13 +1121,13 @@ module.exports = async ( logger.debug("ChannelBalance Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "channelBalance", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("ChannelBalance:", response); @@ -1065,7 +1144,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } return; } @@ -1089,10 +1168,10 @@ module.exports = async ( logger.info("OpenChannelRequest Error:", err); const health = await checkHealth(); if (health.LNDStatus.success && !res.headersSent) { - res.status(500).send({ field: "openChannelRequest", errorMessage: sanitizeLNDError(err.message) }); + res.status(500).json({ field: "openChannelRequest", errorMessage: sanitizeLNDError(err.message) }); } else if (!res.headersSent) { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } }); openedChannel.write(openChannelRequest) @@ -1108,7 +1187,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } const { channelPoint, outputIndex } = req.body; @@ -1136,13 +1215,13 @@ module.exports = async ( if (!res.headersSent) { if (health.LNDStatus.success) { logger.debug("CloseChannelRequest Error:", err); - res.status(400).send({ + res.status(400).json({ field: "closeChannel", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } }); @@ -1157,7 +1236,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } const paymentRequest = { payment_request: req.body.payreq }; @@ -1189,12 +1268,12 @@ module.exports = async ( logger.error("SendPayment Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(500).send({ + res.status(500).json({ errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } }); @@ -1210,7 +1289,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } return false; } @@ -1226,13 +1305,13 @@ module.exports = async ( logger.debug("AddInvoice Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "addInvoice", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } return err; } @@ -1250,7 +1329,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } lightning.signMessage( @@ -1260,10 +1339,10 @@ module.exports = async ( logger.debug("SignMessage Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ field: "signMessage", errorMessage: sanitizeLNDError(err.message) }); + res.status(400).json({ field: "signMessage", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("SignMessage:", response); @@ -1282,10 +1361,10 @@ module.exports = async ( logger.debug("VerifyMessage Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ field: "verifyMessage", errorMessage: sanitizeLNDError(err.message) }); + res.status(400).json({ field: "verifyMessage", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("VerifyMessage:", response); @@ -1303,7 +1382,7 @@ module.exports = async ( res.sendStatus(403); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } const sendCoinsRequest = { addr: req.body.addr, amount: req.body.amount }; @@ -1313,13 +1392,13 @@ module.exports = async ( logger.debug("SendCoins Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ field: "sendCoins", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("SendCoins:", response); @@ -1339,10 +1418,10 @@ module.exports = async ( logger.debug("QueryRoute Error:", err); const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ field: "queryRoute", errorMessage: sanitizeLNDError(err.message) }); + res.status(400).json({ field: "queryRoute", errorMessage: sanitizeLNDError(err.message) }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } logger.debug("QueryRoute:", response); @@ -1365,12 +1444,12 @@ module.exports = async ( if (err) { const health = await checkHealth(); if (health.LNDStatus.success) { - res.status(400).send({ + res.status(400).json({ error: err.message }); } else { res.status(500); - res.send({ errorMessage: "LND is down" }); + res.json({ errorMessage: "LND is down" }); } } else { logger.debug("EstimateFee:", fee); diff --git a/src/server.js b/src/server.js index 3743f2cc..f0f1dbe7 100644 --- a/src/server.js +++ b/src/server.js @@ -1,78 +1,119 @@ -"use strict"; +/** + * @prettier + */ /** * Module dependencies. */ const server = program => { - const Https = require("https"); - const Http = require("http"); - const Express = require("express"); - const LightningServices = require("../utils/lightningServices"); - const app = Express(); - - const FS = require("../utils/fs"); - const bodyParser = require("body-parser"); - const session = require("express-session"); - const methodOverride = require("method-override"); - const { unprotectedRoutes, sensitiveRoutes } = require("../utils/protectedRoutes"); + const Https = require('https') + const Http = require('http') + const Express = require('express') + const LightningServices = require('../utils/lightningServices') + const Encryption = require('../utils/encryptionStore') + const app = Express() + + const FS = require('../utils/fs') + const bodyParser = require('body-parser') + const session = require('express-session') + const methodOverride = require('method-override') + const { + unprotectedRoutes, + sensitiveRoutes, + nonEncryptedRoutes + } = require('../utils/protectedRoutes') // load app default configuration data - const defaults = require("../config/defaults")(program.mainnet); + const defaults = require('../config/defaults')(program.mainnet) // define useful global variables ====================================== - module.useTLS = program.usetls; - module.serverPort = program.serverport || defaults.serverPort; - module.httpsPort = module.serverPort; - module.serverHost = program.serverhost || defaults.serverHost; + module.useTLS = program.usetls + module.serverPort = program.serverport || defaults.serverPort + module.httpsPort = module.serverPort + module.serverHost = program.serverhost || defaults.serverHost // setup winston logging ========== - const logger = require("../config/log")( + const logger = require('../config/log')( program.logfile || defaults.logfile, program.loglevel || defaults.loglevel - ); + ) // utilities functions ================= - require("../utils/server-utils")(module); + require('../utils/server-utils')(module) - logger.info("Mainnet Mode:", !!program.mainnet); + logger.info('Mainnet Mode:', !!program.mainnet) + + const modifyResponseBody = (req, res, next) => { + const deviceId = req.headers['x-shockwallet-device-id'] + const oldSend = res.send + + if (!nonEncryptedRoutes.includes(req.path)) { + res.send = (...args) => { + if (args[0] && args[0].encryptedData && args[0].encryptionKey) { + console.log('Response loop detected', req.path, args[0]) + oldSend.apply(res, args) + } else { + // arguments[0] (or `data`) contains the response body + const authorized = Encryption.isAuthorizedDevice({ deviceId }) + const encryptedMessage = authorized + ? Encryption.encryptMessage({ + message: args[0], + deviceId + }) + : args[0] + args[0] = JSON.stringify(encryptedMessage) + oldSend.apply(res, args) + } + } + } + next() + } const wait = seconds => new Promise(resolve => { - const timer = setTimeout(() => resolve(timer), seconds * 1000); - }); + const timer = setTimeout(() => resolve(timer), seconds * 1000) + }) // eslint-disable-next-line consistent-return const startServer = async () => { try { - LightningServices.setDefaults(program); - await LightningServices.init(); + LightningServices.setDefaults(program) + await LightningServices.init() // init lnd module ================= - const lnd = require("../services/lnd/lnd")(LightningServices.services.lightning); - const auth = require("../services/auth/auth"); + const lnd = require('../services/lnd/lnd')( + LightningServices.services.lightning + ) + const auth = require('../services/auth/auth') app.use(async (req, res, next) => { - console.log("Route:", req.path) + console.log('Route:', req.path) if (unprotectedRoutes[req.method][req.path]) { - next(); + next() } else { try { const response = await auth.validateToken( - req.headers.authorization.replace("Bearer ", "") - ); + req.headers.authorization.replace('Bearer ', '') + ) if (response.valid) { - next(); + next() } else { - res.status(401).json({ field: "authorization", errorMessage: "The authorization token you've supplied is invalid" }); + res.status(401).json({ + field: 'authorization', + errorMessage: + "The authorization token you've supplied is invalid" + }) } } catch (err) { logger.error( - !req.headers.authorization - ? "Please add an Authorization header" + !req.headers.authorization + ? 'Please add an Authorization header' : err - ); - res.status(401).json({ field: "authorization", errorMessage: "Please log in" }); + ) + res + .status(401) + .json({ field: 'authorization', errorMessage: 'Please log in' }) } } - }); + }) app.use((req, res, next) => { if (sensitiveRoutes[req.method][req.path]) { @@ -84,7 +125,7 @@ const server = program => { path: req.path, sessionId: req.sessionId }) - ); + ) } else { console.log( JSON.stringify({ @@ -96,10 +137,10 @@ const server = program => { query: req.query, sessionId: req.sessionId }) - ); + ) } - next(); - }); + next() + }) app.use( session({ secret: defaults.sessionSecret, @@ -108,25 +149,23 @@ const server = program => { rolling: true, saveUninitialized: true }) - ); - app.use(bodyParser.urlencoded({ extended: "true" })); - app.use(bodyParser.json()); - app.use(bodyParser.json({ type: "application/vnd.api+json" })); - app.use(methodOverride()); + ) + app.use(bodyParser.urlencoded({ extended: 'true' })) + app.use(bodyParser.json()) + app.use(bodyParser.json({ type: 'application/vnd.api+json' })) + app.use(methodOverride()) // WARNING // error handler middleware, KEEP 4 parameters as express detects the // arity of the function to treat it as a err handling middleware // eslint-disable-next-line no-unused-vars app.use((err, _, res, __) => { // Do logging and user-friendly error message display - logger.error(err); - res - .status(500) - .send({ status: 500, errorMessage: "internal error" }); - }); + logger.error(err) + res.status(500).send({ status: 500, errorMessage: 'internal error' }) + }) const CA = LightningServices.servicesConfig.lndCertPath - const CA_KEY = CA.replace("cert", "key") + const CA_KEY = CA.replace('cert', 'key') const createServer = async () => { try { @@ -134,62 +173,60 @@ const server = program => { const [key, cert] = await Promise.all([ FS.readFile(CA_KEY), FS.readFile(CA) - ]); - const httpsServer = Https.createServer({ key, cert }, app); + ]) + const httpsServer = Https.createServer({ key, cert }, app) - return httpsServer; + return httpsServer } - const httpServer = Http.Server(app); - return httpServer; + const httpServer = Http.Server(app) + return httpServer } catch (err) { - logger.error(err.message); - logger.error("An error has occurred while finding an LND cert to use to open an HTTPS server") - logger.warn("Falling back to opening an HTTP server...") - const httpServer = Http.Server(app); + logger.error(err.message) + logger.error( + 'An error has occurred while finding an LND cert to use to open an HTTPS server' + ) + logger.warn('Falling back to opening an HTTP server...') + const httpServer = Http.Server(app) return httpServer } - }; + } - const serverInstance = await createServer(); + const serverInstance = await createServer() - const io = require("socket.io")(serverInstance); + const io = require('socket.io')(serverInstance) - const Sockets = require("./sockets")( + const Sockets = require('./sockets')( io, lnd, program.user, program.pwd, program.limituser, program.limitpwd - ); + ) - require("./routes")( - app, - defaults, - Sockets, - { - serverHost: module.serverHost, - serverPort: module.serverPort, - usetls: program.usetls, - CA, - CA_KEY - } - ); + require('./routes')(app, defaults, Sockets, { + serverHost: module.serverHost, + serverPort: module.serverPort, + usetls: program.usetls, + CA, + CA_KEY + }) // enable CORS headers - app.use(require("./cors")); + app.use(require('./cors')) // app.use(bodyParser.json({limit: '100000mb'})); - app.use(bodyParser.json({ limit: "50mb" })); - app.use(bodyParser.urlencoded({ limit: "50mb", extended: true })); + app.use(bodyParser.json({ limit: '50mb' })) + app.use(bodyParser.urlencoded({ limit: '50mb', extended: true })) + app.use(modifyResponseBody) - serverInstance.listen(module.serverPort, module.serverhost); + serverInstance.listen(module.serverPort, module.serverhost) logger.info( - "App listening on " + module.serverHost + " port " + module.serverPort - ); + 'App listening on ' + module.serverHost + ' port ' + module.serverPort + ) - module.server = serverInstance; + module.server = serverInstance // const localtunnel = require('localtunnel'); // @@ -198,15 +235,15 @@ const server = program => { // console.log('t', t.url); // }); } catch (err) { - logger.info(err); - logger.info("Restarting server in 30 seconds..."); - await wait(30); - startServer(); - return false; + logger.info(err) + logger.info('Restarting server in 30 seconds...') + await wait(30) + startServer() + return false } - }; + } - startServer(); -}; + startServer() +} -module.exports = server; +module.exports = server diff --git a/utils/encryptionStore.js b/utils/encryptionStore.js index 3e5c75f4..026d381e 100644 --- a/utils/encryptionStore.js +++ b/utils/encryptionStore.js @@ -6,52 +6,130 @@ const { Buffer } = require('buffer') const APIKeyPair = new Map() const authorizedDevices = new Map() -module.exports = { - encrypt: ({ deviceId, message }) => { +const Encryption = { + encryptKey: ({ deviceId, message }) => { if (!authorizedDevices.has(deviceId)) { throw { field: 'deviceId', message: 'Unknown Device ID' } } const devicePublicKey = authorizedDevices.get(deviceId) const data = Buffer.from(message) - const encryptedData = Crypto.publicEncrypt(devicePublicKey, data) + const encryptedData = Crypto.publicEncrypt( + { + key: devicePublicKey, + padding: Crypto.constants.RSA_PKCS1_PADDING + }, + data + ) return encryptedData.toString('base64') }, - decrypt: ({ deviceId, message }) => { + decryptKey: ({ deviceId, message }) => { if (!authorizedDevices.has(deviceId)) { throw { field: 'deviceId', message: 'Unknown Device ID' } } const data = Buffer.from(message, 'base64') - const encryptedData = Crypto.privateDecrypt(APIKeyPair.private, data) - return encryptedData.toString('base64') + const encryptedData = Crypto.privateDecrypt( + { + key: APIKeyPair.get(deviceId).privateKey, + padding: Crypto.constants.RSA_PKCS1_PADDING + }, + data + ) + console.log('Decrypted Data:', encryptedData) + return encryptedData.toString() + }, + encryptMessage: ({ deviceId, message }) => { + const parsedMessage = + typeof message === 'object' ? JSON.stringify(message) : message + const data = Buffer.from(parsedMessage) + const key = Crypto.randomBytes(32) + const iv = Crypto.randomBytes(16) + const encryptedKey = Encryption.encryptKey({ + deviceId, + message: key.toString('hex') + }) + const cipher = Crypto.createCipheriv('aes-256-cbc', key, iv) + const encryptedCipher = cipher.update(data) + const encryptedBuffer = Buffer.concat([ + Buffer.from(encryptedCipher), + Buffer.from(cipher.final()) + ]) + const encryptedData = encryptedBuffer.toString('base64') + return { encryptedData, encryptedKey, iv: iv.toString('hex') } + }, + decryptMessage: ({ message, key, iv }) => { + const data = Buffer.from(message, 'base64') + const cipher = Crypto.createDecipheriv( + 'aes-256-cbc', + Buffer.from(key, 'hex'), + Buffer.from(iv, 'hex') + ) + const decryptedCipher = cipher.update(data) + const decryptedBuffer = Buffer.concat([ + Buffer.from(decryptedCipher), + Buffer.from(cipher.final()) + ]) + const decryptedData = decryptedBuffer.toString() + console.log('Decrypted Data:', decryptedData) + return decryptedData.toString() + }, + isAuthorizedDevice: ({ deviceId }) => { + console.log( + 'deviceId', + deviceId, + Object.fromEntries(authorizedDevices.entries()), + authorizedDevices.has(deviceId) + ) + if (authorizedDevices.has(deviceId)) { + return true + } + + return false }, authorizeDevice: ({ deviceId, publicKey }) => new Promise((resolve, reject) => { if (authorizedDevices.has(deviceId)) { - const error = { success: false, message: 'Device already exists' } - reject(error) - return error + const devicePublicKey = APIKeyPair.get(deviceId).publicKey + const deviceExists = { + success: true, + APIPublicKey: devicePublicKey + } + resolve(deviceExists) + return deviceExists } authorizedDevices.set(deviceId, publicKey) - Crypto.generateKeyPair( 'rsa', { - modulusLength: 4096 + modulusLength: 4096, + privateKeyEncoding: { + type: 'pkcs1', + format: 'pem' + }, + publicKeyEncoding: { + type: 'pkcs1', + format: 'pem' + } }, (err, publicKey, privateKey) => { if (err) { - reject({ field: 'APIKeyPair', errorMessage: err }) + console.error(err) + reject(err) return err } - APIKeyPair.set(deviceId, { + const exportedKey = { publicKey, privateKey + } + + APIKeyPair.set(deviceId, exportedKey) + resolve({ + success: true, + APIPublicKey: exportedKey.publicKey }) - resolve({ success: true }) } ) }), @@ -59,3 +137,5 @@ module.exports = { authorizedDevices.delete(deviceId) } } + +module.exports = Encryption diff --git a/utils/protectedRoutes.js b/utils/protectedRoutes.js index a0b53ca7..8ece4f12 100644 --- a/utils/protectedRoutes.js +++ b/utils/protectedRoutes.js @@ -13,7 +13,8 @@ module.exports = { "/api/lnd/connect": true, "/api/lnd/wallet": true, "/api/lnd/wallet/existing": true, - "/api/lnd/auth": true + "/api/lnd/auth": true, + "/api/security/exchangeKeys": true }, PUT: {}, DELETE: {} @@ -26,5 +27,6 @@ module.exports = { }, PUT: {}, DELETE: {} - } + }, + nonEncryptedRoutes: ['/api/security/exchangeKeys', '/healthz', '/ping', '/api/lnd/wallet/status'] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 77699c0c..7abb87b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -873,7 +873,7 @@ ascli@~1: colour "~0.7.1" optjs "~3.2.2" -asn1@~0.2.3: +asn1@^0.2.4, asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== @@ -4341,6 +4341,13 @@ node-pre-gyp@^0.13.0: semver "^5.3.0" tar "^4" +node-rsa@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.0.7.tgz#85b7a6d6fa8ee624be6402a6b41be49272d58055" + integrity sha512-idwRXma6scFufZmbaKkHpJoLL93yynRefP6yur13wZ5i9FR35ex451KCoF2OORDeJanyRVahmjjiwmUlCnTqJA== + dependencies: + asn1 "^0.2.4" + nodemon@^1.19.3: version "1.19.3" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-1.19.3.tgz#db71b3e62aef2a8e1283a9fa00164237356102c0" From 86348d07287d9a1cf9a94943af49d49dc992f3d7 Mon Sep 17 00:00:00 2001 From: emad-salah Date: Tue, 21 Jan 2020 11:03:11 +0100 Subject: [PATCH 3/5] GET requests end-to-end encryption fix --- src/routes.js | 7 ++++++- utils/encryptionStore.js | 10 ---------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/routes.js b/src/routes.js index f0db22d6..818ec899 100644 --- a/src/routes.js +++ b/src/routes.js @@ -224,7 +224,12 @@ module.exports = async ( console.error("Unknown Device", error) return res.status(401).json(error); } - + + if (req.method === "GET") { + console.log("Method:", req.method); + return next(); + } + console.log("Body:", req.body) console.log("Decrypt params:", { deviceId, message: req.body.encryptionKey }) const decryptedKey = Encryption.decryptKey({ deviceId, message: req.body.encryptionKey }); diff --git a/utils/encryptionStore.js b/utils/encryptionStore.js index 026d381e..7251625d 100644 --- a/utils/encryptionStore.js +++ b/utils/encryptionStore.js @@ -89,16 +89,6 @@ const Encryption = { }, authorizeDevice: ({ deviceId, publicKey }) => new Promise((resolve, reject) => { - if (authorizedDevices.has(deviceId)) { - const devicePublicKey = APIKeyPair.get(deviceId).publicKey - const deviceExists = { - success: true, - APIPublicKey: devicePublicKey - } - resolve(deviceExists) - return deviceExists - } - authorizedDevices.set(deviceId, publicKey) Crypto.generateKeyPair( 'rsa', From a1a31557b5729843d9c329f071100f7521248d2f Mon Sep 17 00:00:00 2001 From: emad-salah Date: Wed, 22 Jan 2020 18:21:52 +0100 Subject: [PATCH 4/5] Socket.io encryption wrapper completed! --- services/gunDB/Mediator/index.js | 117 ++++++++++++++++++++++++++----- utils/encryptionStore.js | 6 -- 2 files changed, 99 insertions(+), 24 deletions(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index be27bf87..c907272b 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -4,6 +4,7 @@ const Gun = require('gun') const debounce = require('lodash/debounce') const once = require('lodash/once') +const Encryption = require('../../../utils/encryptionStore') /** @type {import('../contact-api/SimpleGUN').ISEA} */ // @ts-ignore @@ -96,6 +97,7 @@ const Action = require('../action-constants.js') const API = require('../contact-api/index') const Config = require('../config') const Event = require('../event-constants') +// const { nonEncryptedRoutes } = require('../../../utils/protectedRoutes') /** * @typedef {import('../contact-api/SimpleGUN').GUNNode} GUNNode @@ -279,33 +281,110 @@ class Mediator { * @param {Readonly} socket */ constructor(socket) { - this.socket = socket + this.socket = this.encryptSocketInstance(socket) this.connected = true - socket.on('disconnect', this.onDisconnect) + this.socket.on('disconnect', this.onDisconnect) - socket.on(Action.ACCEPT_REQUEST, this.acceptRequest) - socket.on(Action.BLACKLIST, this.blacklist) - socket.on(Action.GENERATE_NEW_HANDSHAKE_NODE, this.generateHandshakeNode) - socket.on(Action.SEND_HANDSHAKE_REQUEST, this.sendHandshakeRequest) - socket.on( + this.socket.on(Action.ACCEPT_REQUEST, this.acceptRequest) + this.socket.on(Action.BLACKLIST, this.blacklist) + this.socket.on( + Action.GENERATE_NEW_HANDSHAKE_NODE, + this.generateHandshakeNode + ) + this.socket.on(Action.SEND_HANDSHAKE_REQUEST, this.sendHandshakeRequest) + this.socket.on( Action.SEND_HANDSHAKE_REQUEST_WITH_INITIAL_MSG, this.sendHRWithInitialMsg ) - socket.on(Action.SEND_MESSAGE, this.sendMessage) - socket.on(Action.SET_AVATAR, this.setAvatar) - socket.on(Action.SET_DISPLAY_NAME, this.setDisplayName) + this.socket.on(Action.SEND_MESSAGE, this.sendMessage) + this.socket.on(Action.SET_AVATAR, this.setAvatar) + this.socket.on(Action.SET_DISPLAY_NAME, this.setDisplayName) - socket.on(Event.ON_AVATAR, this.onAvatar) - socket.on(Event.ON_BLACKLIST, this.onBlacklist) - socket.on(Event.ON_CHATS, this.onChats) - socket.on(Event.ON_DISPLAY_NAME, this.onDisplayName) - socket.on(Event.ON_HANDSHAKE_ADDRESS, this.onHandshakeAddress) - socket.on(Event.ON_RECEIVED_REQUESTS, this.onReceivedRequests) - socket.on(Event.ON_SENT_REQUESTS, this.onSentRequests) + this.socket.on(Event.ON_AVATAR, this.onAvatar) + this.socket.on(Event.ON_BLACKLIST, this.onBlacklist) + this.socket.on(Event.ON_CHATS, this.onChats) + this.socket.on(Event.ON_DISPLAY_NAME, this.onDisplayName) + this.socket.on(Event.ON_HANDSHAKE_ADDRESS, this.onHandshakeAddress) + this.socket.on(Event.ON_RECEIVED_REQUESTS, this.onReceivedRequests) + this.socket.on(Event.ON_SENT_REQUESTS, this.onSentRequests) - socket.on(IS_GUN_AUTH, this.isGunAuth) + this.socket.on(IS_GUN_AUTH, this.isGunAuth) + } + + encryptSocketInstance = socket => { + return { + on: (eventName, cb) => { + const deviceId = socket.handshake.query['x-shockwallet-device-id'] + socket.on(eventName, data => { + try { + // if (nonEncryptedEvents.includes(eventName)) { + // return cb(data) + // } + + if (!data) { + return cb(data) + } + + if (!deviceId) { + const error = { + field: 'deviceId', + message: 'Please specify a device ID' + } + console.error(error) + return false + } + + if (!Encryption.isAuthorizedDevice({ deviceId })) { + const error = { + field: 'deviceId', + message: 'Please specify a device ID' + } + console.error('Unknown Device', error) + return false + } + + console.log('Event:', eventName) + console.log('Data:', data) + console.log('Decrypt params:', { + deviceId, + message: data.encryptedKey + }) + const decryptedKey = Encryption.decryptKey({ + deviceId, + message: data.encryptedKey + }) + const decryptedMessage = Encryption.decryptMessage({ + message: data.encryptedData, + key: decryptedKey, + iv: data.iv + }) + const decryptedData = JSON.parse(decryptedMessage) + return cb(decryptedData) + } catch (err) { + console.error(err) + return false + } + }) + }, + emit: (eventName, data) => { + try { + const deviceId = socket.handshake.query['x-shockwallet-device-id'] + const authorized = Encryption.isAuthorizedDevice({ deviceId }) + const encryptedMessage = authorized + ? Encryption.encryptMessage({ + message: data, + deviceId + }) + : data + console.log('Sending Message...', eventName, data, encryptedMessage) + socket.emit(eventName, encryptedMessage) + } catch (err) { + console.error(err) + } + } + } } isGunAuth = () => { @@ -686,6 +765,8 @@ class Mediator { try { const { token } = body + console.log('ON_CHATS', body) + await throwOnInvalidToken(token) API.Events.onChats( diff --git a/utils/encryptionStore.js b/utils/encryptionStore.js index 7251625d..a661de65 100644 --- a/utils/encryptionStore.js +++ b/utils/encryptionStore.js @@ -75,12 +75,6 @@ const Encryption = { return decryptedData.toString() }, isAuthorizedDevice: ({ deviceId }) => { - console.log( - 'deviceId', - deviceId, - Object.fromEntries(authorizedDevices.entries()), - authorizedDevices.has(deviceId) - ) if (authorizedDevices.has(deviceId)) { return true } From 5173a95d24b58e08321597956933caf8277dab47 Mon Sep 17 00:00:00 2001 From: boufni95 <33513308+boufni95@users.noreply.github.com> Date: Thu, 30 Jan 2020 21:17:51 +0100 Subject: [PATCH 5/5] parse to json object in case of string body --- services/gunDB/Mediator/index.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 6ecd4476..2aa3e827 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -350,7 +350,9 @@ class Mediator { console.error('Unknown Device', error) return false } - + if(typeof data === 'string'){ + data = JSON.parse(data) + } console.log('Event:', eventName) console.log('Data:', data) console.log('Decrypt params:', {