diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 009ef65f..cb1cc805 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -205,7 +205,7 @@ const Config = require('../config') // TO DO: move to common repo /** * @typedef {object} SimpleSocket - * @prop {(eventName: string, data: Emission|EncryptedEmission) => void} emit + * @prop {(eventName: string, data?: Emission|EncryptedEmission) => void} emit * @prop {(eventName: string, handler: (data: any) => void) => void} on * @prop {{ query: { 'x-shockwallet-device-id': string }}} handshake */ @@ -1475,5 +1475,6 @@ module.exports = { getUser, mySEA, getMySecret, - freshGun + freshGun, + $$__SHOCKWALLET__ENCRYPTED__ } diff --git a/services/gunDB/rpc.js b/services/gunDB/rpc.js index 66213000..3611789b 100644 --- a/services/gunDB/rpc.js +++ b/services/gunDB/rpc.js @@ -7,11 +7,53 @@ const mapValues = require('lodash/mapValues') const Bluebird = require('bluebird') const { pubToEpub } = require('./contact-api/utils') -const { getGun, getUser, mySEA: SEA, getMySecret } = require('./Mediator') +const { + getGun, + getUser, + mySEA: SEA, + getMySecret, + $$__SHOCKWALLET__ENCRYPTED__ +} = require('./Mediator') /** * @typedef {import('./contact-api/SimpleGUN').ValidDataValue} ValidDataValue */ +/** + * @param {ValidDataValue} value + * @param {string} publicKey + * @returns {Promise} + */ +const deepDecryptIfNeeded = async (value, publicKey) => { + if (Schema.isObj(value)) { + return Bluebird.props( + mapValues(value, o => deepDecryptIfNeeded(o, publicKey)) + ) + } + + if ( + typeof value === 'string' && + value.indexOf($$__SHOCKWALLET__ENCRYPTED__) === 0 + ) { + const user = getUser() + if (!user.is) { + throw new Error(Constants.ErrorCode.NOT_AUTH) + } + + let sec = '' + if (user.is.pub === publicKey) { + sec = getMySecret() + } else { + sec = await SEA.secret(publicKey, user._.sea) + } + + const decrypted = SEA.decrypt(value, sec) + + return decrypted + } + + return value +} + /** * @param {ValidDataValue} value * @returns {Promise} @@ -148,5 +190,7 @@ const set = async (rawPath, value) => { module.exports = { put, - set + set, + deepDecryptIfNeeded, + deepEncryptIfNeeded } diff --git a/src/routes.js b/src/routes.js index eb78f6da..70cf450a 100644 --- a/src/routes.js +++ b/src/routes.js @@ -2995,12 +2995,19 @@ module.exports = async ( * @prop {boolean} startFromUserGraph * @prop {string} path * @prop {string=} publicKey + * @prop {string=} publicKeyForDecryption */ /** * @param {HandleGunFetchParams} args0 * @returns {Promise} */ - const handleGunFetch = ({ type, startFromUserGraph, path, publicKey }) => { + const handleGunFetch = ({ + type, + startFromUserGraph, + path, + publicKey, + publicKeyForDecryption + }) => { const keys = path.split('.') const { tryAndWait } = require('../services/gunDB/contact-api/utils') console.log(keys) @@ -3014,76 +3021,106 @@ module.exports = async ( keys.forEach(key => (node = node.get(key))) return new Promise(res => { - if (type === 'once') node.once(data => res(data)) - if (type === 'load') node.load(data => res(data)) + const listener = async data => { + if (publicKeyForDecryption) { + res( + await GunWriteRPC.deepDecryptIfNeeded( + data, + publicKeyForDecryption + ) + ) + } else { + res(data) + } + } + + if (type === 'once') node.once(listener) + if (type === 'load') node.load(listener) }) }) } + /** + * Used decryption of incoming data. + */ + const PUBKEY_FOR_DECRYPT_HEADER = 'public-key-for-decryption' + ap.get('/api/gun/once/:path', async (req, res) => { + const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const { path } = req.params res.status(200).json({ data: await handleGunFetch({ path, startFromUserGraph: false, - type: 'once' + type: 'once', + publicKeyForDecryption }) }) }) ap.get('/api/gun/load/:path', async (req, res) => { + const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const { path } = req.params res.status(200).json({ data: await handleGunFetch({ path, startFromUserGraph: false, - type: 'load' + type: 'load', + publicKeyForDecryption }) }) }) ap.get('/api/gun/user/once/:path', async (req, res) => { + const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const { path } = req.params res.status(200).json({ data: await handleGunFetch({ path, startFromUserGraph: true, - type: 'once' + type: 'once', + publicKeyForDecryption }) }) }) ap.get('/api/gun/user/load/:path', async (req, res) => { + const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const { path } = req.params res.status(200).json({ data: await handleGunFetch({ path, startFromUserGraph: true, - type: 'load' + type: 'load', + publicKeyForDecryption }) }) }) ap.get('/api/gun/otheruser/:publicKey/once/:path', async (req, res) => { + const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const { path, publicKey } = req.params res.status(200).json({ data: await handleGunFetch({ path, startFromUserGraph: false, type: 'once', - publicKey + publicKey, + publicKeyForDecryption }) }) }) ap.get('/api/gun/otheruser/:publicKey/load/:path', async (req, res) => { + const publicKeyForDecryption = req.header(PUBKEY_FOR_DECRYPT_HEADER) const { path, publicKey } = req.params res.status(200).json({ data: await handleGunFetch({ path, startFromUserGraph: false, type: 'load', - publicKey + publicKey, + publicKeyForDecryption }) }) }) diff --git a/src/sockets.js b/src/sockets.js index 4feecfdc..5a8b4890 100644 --- a/src/sockets.js +++ b/src/sockets.js @@ -1,7 +1,10 @@ -/** @prettier */ +/** + * @format + */ // @ts-check const logger = require('winston') + const Encryption = require('../utils/encryptionStore') const LightningServices = require('../utils/lightningServices') const { @@ -9,7 +12,16 @@ const { getUser, isAuthenticated } = require('../services/gunDB/Mediator') +const { deepDecryptIfNeeded } = require('../services/gunDB/rpc') +/** + * @typedef {import('../services/gunDB/Mediator').SimpleSocket} SimpleSocket + * @typedef {import('../services/gunDB/contact-api/SimpleGUN').ValidDataValue} ValidDataValue + */ +/** + * @param {SimpleSocket} socket + * @param {string} subID + */ const onPing = (socket, subID) => { logger.warn('Subscribing to pings socket...' + subID) @@ -296,7 +308,7 @@ module.exports = ( return } - const { $shock } = socket.handshake.query + const { $shock, publicKeyForDecryption } = socket.handshake.query const [root, path, method] = $shock.split('::') @@ -316,12 +328,21 @@ module.exports = ( } /** - * @param {unknown} data + * @param {ValidDataValue} data * @param {string} key */ - const listener = (data, key) => { + const listener = async (data, key) => { try { - socket.emit('$shock', data, key) + if (publicKeyForDecryption) { + const decData = await deepDecryptIfNeeded( + data, + publicKeyForDecryption + ) + + socket.emit('$shock', decData, key) + } else { + socket.emit('$shock', data, key) + } } catch (err) { logger.error( `Error for gun rpc socket, query ${$shock} -> ${err.message}`