From 219de3b67fc66ededd94c4fddad38e9a50522dcc Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 15:57:55 -0400 Subject: [PATCH 01/32] remove unnecessary check --- services/gunDB/contact-api/actions.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 9fe5300c..160f6934 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -408,18 +408,8 @@ const sendHandshakeRequest = async (recipientPublicKey, gun, user, SEA) => { console.log('sendHR() -> before mySecret') const mySecret = await SEA.secret(user._.sea.epub, user._.sea) - if (typeof mySecret !== 'string') { - throw new TypeError( - "sendHandshakeRequest() -> typeof mySecret !== 'string'" - ) - } console.log('sendHR() -> before ourSecret') const ourSecret = await SEA.secret(recipientEpub, user._.sea) - if (typeof ourSecret !== 'string') { - throw new TypeError( - "sendHandshakeRequest() -> typeof ourSecret !== 'string'" - ) - } // check if successful handshake is present From 40f1e939d75ab20c24f505d816c40dfe722512b1 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 15:59:18 -0400 Subject: [PATCH 02/32] order schema --- services/gunDB/contact-api/schema.js | 41 ++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/services/gunDB/contact-api/schema.js b/services/gunDB/contact-api/schema.js index 929534c9..9a7d57bf 100644 --- a/services/gunDB/contact-api/schema.js +++ b/services/gunDB/contact-api/schema.js @@ -1,5 +1,5 @@ /** - * @prettier + * @format */ /** * @typedef {object} HandshakeRequest @@ -316,7 +316,6 @@ exports.isPartialOutgoing = item => { } /** - * * @param {any} item * @returns {item is Outgoing} */ @@ -337,3 +336,41 @@ exports.isOutgoing = item => { return typeof obj.with === 'string' && messagesAreMessages } + +/** + * @typedef {object} Order + * @prop {string} from Public key of sender. + * @prop {string} amount Encrypted + * @prop {string} memo Encrypted + * @prop {number} timestamp + */ + +/** + * @param {any} item + * @returns {item is Order} + */ +exports.isOrder = item => { + if (typeof item !== 'object') { + return false + } + + if (item === null) { + return false + } + + const obj = /** @type {Order} */ (item) + + if (typeof obj.amount !== 'string') { + return false + } + + if (typeof obj.from !== 'string') { + return false + } + + if (typeof obj.memo !== 'string') { + return false + } + + return typeof obj.timestamp === 'number' +} From 500e7eb2bf1e9d91f2867840dfc0851ceda9d32d Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 15:59:24 -0400 Subject: [PATCH 03/32] order error --- services/gunDB/contact-api/errorCode.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/gunDB/contact-api/errorCode.js b/services/gunDB/contact-api/errorCode.js index eddc4f84..c3785d4a 100644 --- a/services/gunDB/contact-api/errorCode.js +++ b/services/gunDB/contact-api/errorCode.js @@ -41,3 +41,5 @@ exports.ALREADY_REQUESTED_HANDSHAKE = 'ALREADY_REQUESTED_HANDSHAKE' exports.STALE_HANDSHAKE_ADDRESS = 'STALE_HANDSHAKE_ADDRESS' exports.TIMEOUT_ERR = 'TIMEOUT_ERR' + +exports.ORDER_NOT_ANSWERED_IN_TIME = 'ORDER_NOT_ANSWERED_IN_TIME' From f638003543a7537127240cbcb4390134dc4f9014 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 15:59:38 -0400 Subject: [PATCH 04/32] order keys --- services/gunDB/contact-api/key.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/services/gunDB/contact-api/key.js b/services/gunDB/contact-api/key.js index 1aa3f8a0..52d7de8b 100644 --- a/services/gunDB/contact-api/key.js +++ b/services/gunDB/contact-api/key.js @@ -24,3 +24,9 @@ exports.DISPLAY_NAME = 'displayName' * Maps user to the last request sent to them. */ exports.USER_TO_LAST_REQUEST_SENT = 'USER_TO_LAST_REQUEST_SENT' + +exports.CURRENT_ORDER_ADDRESS = 'currentOrderAddress' + +exports.ORDER_NODES = 'orderNodes' + +exports.ORDER_TO_RESPONSE = 'orderToResponse' From 3b7b5d345c4d035c2d458c4b0b37e6600484030c Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 16:01:37 -0400 Subject: [PATCH 05/32] order getters --- services/gunDB/contact-api/getters.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 services/gunDB/contact-api/getters.js diff --git a/services/gunDB/contact-api/getters.js b/services/gunDB/contact-api/getters.js new file mode 100644 index 00000000..7687fdbb --- /dev/null +++ b/services/gunDB/contact-api/getters.js @@ -0,0 +1,15 @@ +const Key = require('./key') +/** + * @param {string} pub + * @param {import('./SimpleGUN').GUNNode} gun + * @returns {Promise} + */ +exports.currentOrderAddress = async (pub, gun) => { + const currAddr = await gun.user(pub).get(Key.CURRENT_ORDER_ADDRESS).then() + + if (typeof currAddr !== 'string') { + throw new TypeError('Expected user.currentOrderAddress to be an string') + } + + return currAddr +} \ No newline at end of file From 93960e93172eff9177e41cb1435911be41420a7f Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 16:03:36 -0400 Subject: [PATCH 06/32] onOrders job --- services/gunDB/contact-api/jobs/index.js | 18 ++++ .../{jobs.js => jobs/onAcceptedRequests.js} | 29 ++----- services/gunDB/contact-api/jobs/onOrders.js | 82 +++++++++++++++++++ 3 files changed, 109 insertions(+), 20 deletions(-) create mode 100644 services/gunDB/contact-api/jobs/index.js rename services/gunDB/contact-api/{jobs.js => jobs/onAcceptedRequests.js} (80%) create mode 100644 services/gunDB/contact-api/jobs/onOrders.js diff --git a/services/gunDB/contact-api/jobs/index.js b/services/gunDB/contact-api/jobs/index.js new file mode 100644 index 00000000..55c993e1 --- /dev/null +++ b/services/gunDB/contact-api/jobs/index.js @@ -0,0 +1,18 @@ +/** + * @format + * Jobs are subscriptions to events that perform actions (write to GUN) on + * response to certain ways events can happen. These tasks need to be fired up + * at app launch otherwise certain features won't work as intended. Tasks should + * ideally be idempotent, that is, if they were to be fired up after a certain + * amount of time after app launch, everything should work as intended. For this + * to work, special care has to be put into how these respond to events. These + * tasks accept factories that are homonymous to the events on this same module. + */ + +const onAcceptedRequests = require('./onAcceptedRequests') +const onOrders = require('./onOrders') + +module.exports = { + onAcceptedRequests, + onOrders +} diff --git a/services/gunDB/contact-api/jobs.js b/services/gunDB/contact-api/jobs/onAcceptedRequests.js similarity index 80% rename from services/gunDB/contact-api/jobs.js rename to services/gunDB/contact-api/jobs/onAcceptedRequests.js index a6ec48dd..34888ca9 100644 --- a/services/gunDB/contact-api/jobs.js +++ b/services/gunDB/contact-api/jobs/onAcceptedRequests.js @@ -1,24 +1,15 @@ /** - * @prettier - * Taks are subscriptions to events that perform actions (write to GUN) on - * response to certain ways events can happen. These tasks need to be fired up - * at app launch otherwise certain features won't work as intended. Tasks should - * ideally be idempotent, that is, if they were to be fired up after a certain - * amount of time after app launch, everything should work as intended. For this - * to work, special care has to be put into how these respond to events. These - * tasks could be hardcoded inside events but then they wouldn't be easily - * auto-testable. These tasks accept factories that are homonymous to the events - * on the same + * @format */ -const ErrorCode = require('./errorCode') -const Key = require('./key') -const Schema = require('./schema') -const Utils = require('./utils') +const ErrorCode = require('../errorCode') +const Key = require('../key') +const Schema = require('../schema') +const Utils = require('../utils') /** - * @typedef {import('./SimpleGUN').GUNNode} GUNNode - * @typedef {import('./SimpleGUN').ISEA} ISEA - * @typedef {import('./SimpleGUN').UserGUNNode} UserGUNNode + * @typedef {import('../SimpleGUN').GUNNode} GUNNode + * @typedef {import('../SimpleGUN').ISEA} ISEA + * @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode */ /** @@ -148,6 +139,4 @@ const onAcceptedRequests = async (user, SEA) => { }) } -module.exports = { - onAcceptedRequests -} +module.exports = onAcceptedRequests diff --git a/services/gunDB/contact-api/jobs/onOrders.js b/services/gunDB/contact-api/jobs/onOrders.js new file mode 100644 index 00000000..95043e34 --- /dev/null +++ b/services/gunDB/contact-api/jobs/onOrders.js @@ -0,0 +1,82 @@ +/** + * @format + */ +const ErrorCode = require('../errorCode') +const Key = require('../key') +const Schema = require('../schema') +const Utils = require('../utils') + +/** + * @typedef {import('../SimpleGUN').GUNNode} GUNNode + * @typedef {import('../SimpleGUN').ListenerData} ListenerData + * @typedef {import('../SimpleGUN').ISEA} ISEA + * @typedef {import('../SimpleGUN').UserGUNNode} UserGUNNode + */ + +let currentOrderAddr = '' + +/** + * @param {string} addr + * @param {UserGUNNode} user + * @param {ISEA} SEA + * @returns {(order: ListenerData, orderID: string) => void} + */ +const listenerForAddr = (addr, user, SEA) => async (order, orderID) => { + if (addr !== currentOrderAddr) { + return + } + + if (!Schema.isOrder(order)) { + throw new Error(`Expected an order instead got: ${JSON.stringify(order)}`) + } + + const senderEpub = await Utils.pubToEpub(order.from) + const secret = await SEA.secret(senderEpub, user._.sea) + + const amount = Number(await SEA.decrypt(order.amount, secret)) + const memo = await SEA.decrypt(order.memo, secret) + + // create invoice here.. + + const invoice = `INVOICE_${amount}_${memo}` + + const encInvoice = await SEA.encrypt(invoice, secret) + + user + .get(Key.ORDER_TO_RESPONSE) + .get(orderID) + .put(encInvoice, ack => { + if (ack.err) { + console.error(`error saving order response: ${ack.err}`) + } + }) +} + +/** + * @param {UserGUNNode} user + * @param {GUNNode} gun + * @param {ISEA} SEA + * @throws {Error} NOT_AUTH + * @returns {void} + */ +const onOrders = (user, gun, SEA) => { + if (!user.is) { + throw new Error(ErrorCode.NOT_AUTH) + } + + user.get(Key.CURRENT_ORDER_ADDRESS).on(addr => { + if (typeof addr !== 'string') { + throw new TypeError('Expected current order address to be an string') + } + + currentOrderAddr = addr + + gun + .get(Key.ORDER_NODES) + .get(addr) + .map() + .on(listenerForAddr(currentOrderAddr, user, SEA)) + }) +} + +module.exports = onOrders From 17ed0f6a9dadc93f3fcc0389cf3d6c4aadbb634b Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 16:06:28 -0400 Subject: [PATCH 07/32] reject correctly --- services/gunDB/contact-api/actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 160f6934..059d9496 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -650,7 +650,7 @@ const sendMessage = async (recipientPublicKey, body, user, SEA) => { .get(Key.MESSAGES) .set(newMessage, ack => { if (ack.err) { - rej(ack.err) + rej(new Error(ack.err)) } else { res() } From 107b3cde4c8a3d393e078adfdfd6b9afad732977 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 16:10:27 -0400 Subject: [PATCH 08/32] basic delete message --- services/gunDB/contact-api/actions.js | 59 +++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 059d9496..5df71f80 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -658,6 +658,64 @@ const sendMessage = async (recipientPublicKey, body, user, SEA) => { }) } +/** + * @param {string} recipientPub + * @param {string} msgID + * @param {UserGUNNode} user + * @param {ISEA} SEA + * @returns {Promise} + */ +const deleteMessage = async (recipientPub, msgID, user, SEA) => { + if (!user.is) { + throw new Error(ErrorCode.NOT_AUTH) + } + + if (typeof recipientPub !== 'string') { + throw new TypeError( + `expected recipientPublicKey to be an string, but instead got: ${typeof recipientPub}` + ) + } + + if (recipientPub.length === 0) { + throw new TypeError( + 'expected recipientPublicKey to be an string of length greater than zero' + ) + } + + if (typeof msgID !== 'string') { + throw new TypeError( + `expected msgID to be an string, instead got: ${typeof msgID}` + ) + } + + if (msgID.length === 0) { + throw new TypeError( + 'expected msgID to be an string of length greater than zero' + ) + } + + const outgoingID = await Utils.recipientToOutgoingID(recipientPub, user, SEA) + + if (outgoingID === null) { + throw new Error(`Could not fetch an outgoing id for user: ${recipientPub}`) + } + + return new Promise((res, rej) => { + user + .get(Key.OUTGOINGS) + .get(outgoingID) + .get(Key.MESSAGES) + .get(msgID) + .put(null, ack => { + if (ack.err) { + rej(new Error(ack.err)) + } else { + res() + } + }) + }) +} + /** * @param {string|null} avatar * @param {UserGUNNode} user @@ -781,6 +839,7 @@ module.exports = { blacklist, generateHandshakeAddress, sendHandshakeRequest, + deleteMessage, sendMessage, sendHRWithInitialMsg, setAvatar, From ffe28744d95393b57e227bc80a2933a250a49e07 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 16:11:16 -0400 Subject: [PATCH 09/32] send payment action --- services/gunDB/contact-api/actions.js | 89 ++++++++++++++++++++++++++- 1 file changed, 88 insertions(+), 1 deletion(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 5df71f80..c663f353 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -3,6 +3,7 @@ */ const uuidv1 = require('uuid/v1') const ErrorCode = require('./errorCode') +const Getters = require('./getters') const Key = require('./key') const Utils = require('./utils') const { isHandshakeRequest } = require('./schema') @@ -15,6 +16,8 @@ const { isHandshakeRequest } = require('./schema') * @typedef {import('./schema').Message} Message * @typedef {import('./schema').Outgoing} Outgoing * @typedef {import('./schema').PartialOutgoing} PartialOutgoing + * @typedef {import('./schema').Order} Order + * @typedef {import('./SimpleGUN').Ack} Ack */ /** @@ -831,6 +834,89 @@ const sendHRWithInitialMsg = async ( await sendMessage(recipientPublicKey, initialMsg, user, SEA) } +/** + * @param {string} to + * @param {number} amount + * @param {string} memo + * @param {GUNNode} gun + * @param {UserGUNNode} user + * @param {ISEA} SEA + * @throws {Error} If no response in less than 20 seconds from the recipient, or + * lightning cannot find a route for the payment. + * @returns {Promise} + */ +const sendPayment = async (to, amount, memo, gun, user, SEA) => { + const recipientEpub = await Utils.pubToEpub(to) + const ourSecret = await SEA.secret(recipientEpub, user._.sea) + + if (amount < 1) { + throw new RangeError('Amount must be at least 1 sat.') + } + + /** @type {Order} */ + const order = { + amount: await SEA.encrypt(amount.toString(), ourSecret), + from: user._.sea.pub, + memo: await SEA.encrypt(memo, ourSecret), + timestamp: Date.now() + } + + const currOrderAddress = await Getters.currentOrderAddress(to, gun) + + order.timestamp = Date.now() + + /** @type {string} */ + const orderID = await new Promise((res, rej) => { + const ord = gun + .get(Key.ORDER_NODES) + .get(currOrderAddress) + .set(order, ack => { + if (ack.err) { + rej( + new Error( + `Error writing order to order node: ${currOrderAddress} for pub: ${to}: ${ack.err}` + ) + ) + } else { + res(ord._.get) + } + }) + }) + + const bob = gun.user(to) + + /** @type {string} */ + const invoice = await Promise.race([ + new Promise((res, rej) => { + bob + .get(Key.ORDER_TO_RESPONSE) + .get(orderID) + .on(data => { + if (typeof data !== 'string') { + rej( + `Expected order response from pub ${to} to be an string, instead got: ${typeof data}` + ) + } else { + res(data) + } + }) + }), + new Promise((_, rej) => { + setTimeout(() => { + rej(new Error(ErrorCode.ORDER_NOT_ANSWERED_IN_TIME)) + }, 20000) + }) + ]) + + if (Math.random() > 0.5) { + return Promise.resolve() + } + + return Promise.reject( + new Error('Lightning could not find a route to pay invoice: ' + invoice) + ) +} + module.exports = { INITIAL_MSG, __createOutgoingFeed, @@ -843,5 +929,6 @@ module.exports = { sendMessage, sendHRWithInitialMsg, setAvatar, - setDisplayName + setDisplayName, + sendPayment } From 3cd17357febd29793195a0e1c0b0b0db27c82bef Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 16:16:02 -0400 Subject: [PATCH 10/32] spin up onOrders job on auth --- services/gunDB/Mediator/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index be27bf87..35a2debf 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -154,6 +154,7 @@ const authenticate = async (alias, pass) => { } // move this to a subscription; implement off() ? todo API.Jobs.onAcceptedRequests(user, mySEA) + API.Jobs.onOrders(user, gun, mySEA) return user._.sea.pub } @@ -177,6 +178,7 @@ const authenticate = async (alias, pass) => { throw new Error(ack.err) } else if (typeof ack.sea === 'object') { API.Jobs.onAcceptedRequests(user, mySEA) + API.Jobs.onOrders(user, gun, mySEA) const mySec = await mySEA.secret(user._.sea.epub, user._.sea) if (typeof mySec !== 'string') { From 9a10bf324f0420d4089b6e6028ecf7387c69c8d5 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 16:19:57 -0400 Subject: [PATCH 11/32] send payment action constant --- services/gunDB/action-constants.js | 1 + 1 file changed, 1 insertion(+) diff --git a/services/gunDB/action-constants.js b/services/gunDB/action-constants.js index ac17c7a1..440e7bcf 100644 --- a/services/gunDB/action-constants.js +++ b/services/gunDB/action-constants.js @@ -5,6 +5,7 @@ const Actions = { SEND_HANDSHAKE_REQUEST: "SEND_HANDSHAKE_REQUEST", SEND_HANDSHAKE_REQUEST_WITH_INITIAL_MSG: "SEND_HANDSHAKE_REQUEST_WITH_INITIAL_MSG", SEND_MESSAGE: "SEND_MESSAGE", + SEND_PAYMENT: "SEND_PAYMENT", SET_AVATAR: "SET_AVATAR", SET_DISPLAY_NAME: "SET_DISPLAY_NAME" }; From 4947a4dbb6db6ad65cb3a91c1f28571b80e7c36a Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 16:26:00 -0400 Subject: [PATCH 12/32] wire sendpayment to socket --- services/gunDB/Mediator/index.js | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 35a2debf..c7b520f6 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -296,6 +296,7 @@ class Mediator { this.sendHRWithInitialMsg ) socket.on(Action.SEND_MESSAGE, this.sendMessage) + socket.on(Action.SEND_PAYMENT, this.sendPayment) socket.on(Action.SET_AVATAR, this.setAvatar) socket.on(Action.SET_DISPLAY_NAME, this.setDisplayName) @@ -563,6 +564,39 @@ class Mediator { } } + /** + * @param {Readonly<{ uuid: string, recipientPub: string, amount: number, memo: string, token: string }>} reqBody + */ + sendPayment = async reqBody => { + try { + const { recipientPub, amount, memo, token } = reqBody + + await throwOnInvalidToken(token) + + await API.Actions.sendPayment( + recipientPub, + amount, + memo, + gun, + user, + mySEA + ) + + this.socket.emit(Action.SEND_PAYMENT, { + ok: true, + msg: null, + origBody: reqBody + }) + } catch (err) { + console.log(err) + this.socket.emit(Action.SEND_PAYMENT, { + ok: false, + msg: err.message, + origBody: reqBody + }) + } + } + /** * @param {Readonly<{ avatar: string|null , token: string }>} body */ From 20b80bea1323ff3c1417300d2a81c70137d1ee1c Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 18:57:50 -0400 Subject: [PATCH 13/32] encrypted strings must be populated --- services/gunDB/contact-api/actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index c663f353..73db8fbc 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -857,7 +857,7 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { const order = { amount: await SEA.encrypt(amount.toString(), ourSecret), from: user._.sea.pub, - memo: await SEA.encrypt(memo, ourSecret), + memo: await SEA.encrypt(memo || 'no memo', ourSecret), timestamp: Date.now() } From 80c2874bbb18d8a000de88c7039b063b238f7675 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 19:18:23 -0400 Subject: [PATCH 14/32] gen order address & at register --- services/gunDB/Mediator/index.js | 1 + services/gunDB/contact-api/actions.js | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index c7b520f6..79b671cf 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -951,6 +951,7 @@ const register = async (alias, pass) => { return authenticate(alias, pass).then(async pub => { await API.Actions.setDisplayName('anon' + pub.slice(0, 8), user) await API.Actions.generateHandshakeAddress(user) + await API.Actions.generateOrderAddress(user) return pub }) } diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 73db8fbc..fe881211 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -917,6 +917,27 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { ) } +/** + * @param {UserGUNNode} user + * @returns {Promise} + */ +const generateOrderAddress = user => + new Promise((res, rej) => { + if (!user.is) { + throw new Error(ErrorCode.NOT_AUTH) + } + + const address = uuidv1() + + user.get(Key.CURRENT_ORDER_ADDRESS).put(address, ack => { + if (ack.err) { + rej(new Error(ack.err)) + } else { + res() + } + }) + }) + module.exports = { INITIAL_MSG, __createOutgoingFeed, @@ -930,5 +951,6 @@ module.exports = { sendHRWithInitialMsg, setAvatar, setDisplayName, - sendPayment + sendPayment, + generateOrderAddress } From 3e2cea2b86fa68b3bfef9e1cfe3a726a5c8a6f6b Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 19:19:25 -0400 Subject: [PATCH 15/32] decode invoice --- services/gunDB/contact-api/actions.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index fe881211..eb052000 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -912,8 +912,10 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { return Promise.resolve() } + const decInvoice = await SEA.decrypt(invoice, ourSecret) + return Promise.reject( - new Error('Lightning could not find a route to pay invoice: ' + invoice) + new Error('Lightning could not find a route to pay invoice: ' + decInvoice) ) } From 6dcc0d2d04add6d590d2e749be0d72ed8b4af8ae Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 21:28:32 -0400 Subject: [PATCH 16/32] generate invoice for order --- services/gunDB/contact-api/jobs/onOrders.js | 30 +++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/services/gunDB/contact-api/jobs/onOrders.js b/services/gunDB/contact-api/jobs/onOrders.js index 95043e34..fcd13376 100644 --- a/services/gunDB/contact-api/jobs/onOrders.js +++ b/services/gunDB/contact-api/jobs/onOrders.js @@ -1,6 +1,9 @@ /** * @format */ + +const LightningServices = require('../../../../utils/lightningServices') + const ErrorCode = require('../errorCode') const Key = require('../key') const Schema = require('../schema') @@ -36,9 +39,32 @@ const listenerForAddr = (addr, user, SEA) => async (order, orderID) => { const amount = Number(await SEA.decrypt(order.amount, secret)) const memo = await SEA.decrypt(order.memo, secret) - // create invoice here.. + /** + * @type {[any , string]} + */ + const [err, invoice] = await new Promise(resolve => { + const { + services: { lightning } + } = LightningServices - const invoice = `INVOICE_${amount}_${memo}` + lightning.addInvoice( + { + expiry: 36000, + memo, + value: amount + }, + ( + /** @type {any} */ error, + /** @type {{ payment_request: string }} */ response + ) => { + resolve([error, response.payment_request]) + } + ) + }) + + if (err) { + console.log(new Error(err)) + } const encInvoice = await SEA.encrypt(invoice, secret) From 88abc9a2a9c9aa4368ad5829eb73a94744cdf289 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 21:28:40 -0400 Subject: [PATCH 17/32] disable rule --- .eslintrc.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.eslintrc.json b/.eslintrc.json index 8bd8ef46..bfcdeedb 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -62,7 +62,10 @@ "no-shadow": "off", // We're usually throwing objects throughout the API to allow for more detailed error messages - "no-throw-literal": "off" + "no-throw-literal": "off", + + // lightning has sync methods and this rule bans them + "no-sync": "off" }, "parser": "babel-eslint", "env": { From 0734cb6cb924748cdde834aa955b400217277f7d Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 21:28:50 -0400 Subject: [PATCH 18/32] send payment when order invoice is received --- services/gunDB/contact-api/actions.js | 30 ++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index eb052000..b9f4da32 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -2,6 +2,9 @@ * @format */ const uuidv1 = require('uuid/v1') + +const LightningServices = require('../../../utils/lightningServices') + const ErrorCode = require('./errorCode') const Getters = require('./getters') const Key = require('./key') @@ -908,15 +911,28 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { }) ]) - if (Math.random() > 0.5) { - return Promise.resolve() - } - const decInvoice = await SEA.decrypt(invoice, ourSecret) - return Promise.reject( - new Error('Lightning could not find a route to pay invoice: ' + decInvoice) - ) + const { + services: { lightning } + } = LightningServices + + await new Promise((rej, resolve) => { + lightning.sendPaymentSync( + { + payment_request: decInvoice + }, + (/** @type {any} */ err, /** @type {any} */ res) => { + if (err) { + console.log(err) + rej(err) + } else { + console.log(res) + resolve() + } + } + ) + }) } /** From 4e24f81f8af0ca364384befdbb0f4234b69b09e8 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 21:55:27 -0400 Subject: [PATCH 19/32] better error handling --- services/gunDB/contact-api/getters.js | 1 + services/gunDB/contact-api/jobs/onOrders.js | 86 +++++++++++---------- 2 files changed, 48 insertions(+), 39 deletions(-) diff --git a/services/gunDB/contact-api/getters.js b/services/gunDB/contact-api/getters.js index 7687fdbb..f439512a 100644 --- a/services/gunDB/contact-api/getters.js +++ b/services/gunDB/contact-api/getters.js @@ -1,4 +1,5 @@ const Key = require('./key') + /** * @param {string} pub * @param {import('./SimpleGUN').GUNNode} gun diff --git a/services/gunDB/contact-api/jobs/onOrders.js b/services/gunDB/contact-api/jobs/onOrders.js index fcd13376..3db6e350 100644 --- a/services/gunDB/contact-api/jobs/onOrders.js +++ b/services/gunDB/contact-api/jobs/onOrders.js @@ -25,57 +25,65 @@ let currentOrderAddr = '' * @returns {(order: ListenerData, orderID: string) => void} */ const listenerForAddr = (addr, user, SEA) => async (order, orderID) => { - if (addr !== currentOrderAddr) { - return - } + try { + if (addr !== currentOrderAddr) { + return + } - if (!Schema.isOrder(order)) { - throw new Error(`Expected an order instead got: ${JSON.stringify(order)}`) - } + if (!Schema.isOrder(order)) { + throw new Error(`Expected an order instead got: ${JSON.stringify(order)}`) + } - const senderEpub = await Utils.pubToEpub(order.from) - const secret = await SEA.secret(senderEpub, user._.sea) + const orderToResponse = user.get(Key.ORDER_TO_RESPONSE) - const amount = Number(await SEA.decrypt(order.amount, secret)) - const memo = await SEA.decrypt(order.memo, secret) + if (await orderToResponse.get(orderID).then()) { + return + } - /** - * @type {[any , string]} - */ - const [err, invoice] = await new Promise(resolve => { - const { - services: { lightning } - } = LightningServices + const senderEpub = await Utils.pubToEpub(order.from) + const secret = await SEA.secret(senderEpub, user._.sea) - lightning.addInvoice( - { - expiry: 36000, - memo, - value: amount - }, - ( - /** @type {any} */ error, - /** @type {{ payment_request: string }} */ response - ) => { - resolve([error, response.payment_request]) - } - ) - }) + const amount = Number(await SEA.decrypt(order.amount, secret)) + const memo = await SEA.decrypt(order.memo, secret) - if (err) { - console.log(new Error(err)) - } + /** + * @type {string} + */ + const invoice = await new Promise((resolve, rej) => { + const { + services: { lightning } + } = LightningServices - const encInvoice = await SEA.encrypt(invoice, secret) + lightning.addInvoice( + { + expiry: 36000, + memo, + value: amount + }, + ( + /** @type {any} */ error, + /** @type {{ payment_request: string }} */ response + ) => { + if (error) { + rej(error) + } else { + resolve(response.payment_request) + } + } + ) + }) - user - .get(Key.ORDER_TO_RESPONSE) - .get(orderID) - .put(encInvoice, ack => { + const encInvoice = await SEA.encrypt(invoice, secret) + + orderToResponse.get(orderID).put(encInvoice, ack => { if (ack.err) { console.error(`error saving order response: ${ack.err}`) } }) + } catch (err) { + console.error('error inside onOrders:') + console.error(err) + } } /** From a72f6dd09181ecdd77cc9697d59c999a4f32a85c Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Mon, 13 Jan 2020 22:05:53 -0400 Subject: [PATCH 20/32] check auth --- services/gunDB/contact-api/actions.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index b9f4da32..9a994731 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -849,6 +849,10 @@ const sendHRWithInitialMsg = async ( * @returns {Promise} */ const sendPayment = async (to, amount, memo, gun, user, SEA) => { + if (!user.is) { + throw new Error(ErrorCode.NOT_AUTH) + } + const recipientEpub = await Utils.pubToEpub(to) const ourSecret = await SEA.secret(recipientEpub, user._.sea) From c3fff0a432278dc6e6bbbb71085308a6cd369f3f Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 14 Jan 2020 18:08:18 -0400 Subject: [PATCH 21/32] timeout for getter --- services/gunDB/contact-api/actions.js | 2 +- services/gunDB/contact-api/getters.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index 9a994731..e81d5ff5 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -868,7 +868,7 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { timestamp: Date.now() } - const currOrderAddress = await Getters.currentOrderAddress(to, gun) + const currOrderAddress = await Getters.currentOrderAddress(to) order.timestamp = Date.now() diff --git a/services/gunDB/contact-api/getters.js b/services/gunDB/contact-api/getters.js index f439512a..fa704e37 100644 --- a/services/gunDB/contact-api/getters.js +++ b/services/gunDB/contact-api/getters.js @@ -1,12 +1,12 @@ const Key = require('./key') +const Utils = require('./utils') /** * @param {string} pub - * @param {import('./SimpleGUN').GUNNode} gun * @returns {Promise} */ -exports.currentOrderAddress = async (pub, gun) => { - const currAddr = await gun.user(pub).get(Key.CURRENT_ORDER_ADDRESS).then() +exports.currentOrderAddress = async (pub) => { + const currAddr = await Utils.tryAndWait((gun) => gun.user(pub).get(Key.CURRENT_ORDER_ADDRESS).then()) if (typeof currAddr !== 'string') { throw new TypeError('Expected user.currentOrderAddress to be an string') From 8ce9e65a8dd76c6b11464ec013901ce12c4314d5 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 14 Jan 2020 18:26:14 -0400 Subject: [PATCH 22/32] better handle of sendpaymentsync response --- services/gunDB/contact-api/actions.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index e81d5ff5..a9c29cfb 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -921,18 +921,31 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { services: { lightning } } = LightningServices + /** + * Partial + * https://api.lightning.community/#grpc-response-sendresponse-2 + * @typedef {object} SendResponse + * @prop {string|null} payment_error + * @prop {any[]|null} payment_route + */ + await new Promise((rej, resolve) => { lightning.sendPaymentSync( { payment_request: decInvoice }, - (/** @type {any} */ err, /** @type {any} */ res) => { - if (err) { - console.log(err) - rej(err) - } else { - console.log(res) + (_, /** @type {SendResponse} */ res) => { + if (res.payment_error) { + rej(new Error(res.payment_error)) + } else if (res.payment_route) { resolve() + } else { + rej( + new Error( + 'Unexpected response from sendPaymentSync() -> ' + + JSON.stringify(res) + ) + ) } } ) From 47f37b06d8f600f0a76cc7b5db9e76cf7b1e4fb2 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 14 Jan 2020 18:33:20 -0400 Subject: [PATCH 23/32] stringent sendpaymentsync handling --- services/gunDB/contact-api/actions.js | 36 +++++++++++++++++++-------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index a9c29cfb..ad9bf1d3 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -934,18 +934,32 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { { payment_request: decInvoice }, - (_, /** @type {SendResponse} */ res) => { - if (res.payment_error) { - rej(new Error(res.payment_error)) - } else if (res.payment_route) { - resolve() - } else { - rej( - new Error( - 'Unexpected response from sendPaymentSync() -> ' + - JSON.stringify(res) + (/*** @type {any} */ err, /** @type {SendResponse} */ res) => { + console.log(`sendPaymentSync err: ${JSON.stringify(err)}`) + console.log(`sendPaymentSync res: ${JSON.stringify(res)}`) + + if (err || typeof err === 'number') { + rej(new Error(`sendPaymentSync error: ${JSON.stringify(err)}`)) + } else if (res) { + if (res.payment_error) { + rej( + new Error( + `sendPaymentSync error response: ${JSON.stringify(res)}` + ) ) - ) + } else if (!res.payment_route) { + rej( + new Error( + `sendPaymentSync no payment route response: ${JSON.stringify( + res + )}` + ) + ) + } else { + resolve() + } + } else { + rej(new Error('no error or response received from sendPaymentSync')) } } ) From e8ad0b854c99b5ac6c013e67b52f7142304b9465 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Tue, 14 Jan 2020 18:42:10 -0400 Subject: [PATCH 24/32] exact error handling --- services/gunDB/contact-api/actions.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index ad9bf1d3..b6b7de2b 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -921,6 +921,11 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { services: { lightning } } = LightningServices + /** + * @typedef {object} SendErr + * @prop {string} details + */ + /** * Partial * https://api.lightning.community/#grpc-response-sendresponse-2 @@ -929,17 +934,14 @@ const sendPayment = async (to, amount, memo, gun, user, SEA) => { * @prop {any[]|null} payment_route */ - await new Promise((rej, resolve) => { + await new Promise((resolve, rej) => { lightning.sendPaymentSync( { payment_request: decInvoice }, - (/*** @type {any} */ err, /** @type {SendResponse} */ res) => { - console.log(`sendPaymentSync err: ${JSON.stringify(err)}`) - console.log(`sendPaymentSync res: ${JSON.stringify(res)}`) - - if (err || typeof err === 'number') { - rej(new Error(`sendPaymentSync error: ${JSON.stringify(err)}`)) + (/** @type {SendErr=} */ err, /** @type {SendResponse} */ res) => { + if (err) { + rej(new Error(err.details)) } else if (res) { if (res.payment_error) { rej( From 865e553319d1867a58d82627234400e694a942a4 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Wed, 15 Jan 2020 23:43:04 -0400 Subject: [PATCH 25/32] bio --- services/gunDB/Mediator/index.js | 54 +++++++++++++++++++++++++++ services/gunDB/action-constants.js | 3 +- services/gunDB/contact-api/actions.js | 36 +++++++++++++++++- services/gunDB/contact-api/events.js | 29 +++++++++++++- services/gunDB/contact-api/key.js | 2 + services/gunDB/event-constants.js | 3 +- 6 files changed, 123 insertions(+), 4 deletions(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 79b671cf..ef1b3d98 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -299,6 +299,7 @@ class Mediator { socket.on(Action.SEND_PAYMENT, this.sendPayment) socket.on(Action.SET_AVATAR, this.setAvatar) socket.on(Action.SET_DISPLAY_NAME, this.setDisplayName) + socket.on(Action.SET_BIO, this.setBio) socket.on(Event.ON_AVATAR, this.onAvatar) socket.on(Event.ON_BLACKLIST, this.onBlacklist) @@ -307,6 +308,7 @@ class Mediator { socket.on(Event.ON_HANDSHAKE_ADDRESS, this.onHandshakeAddress) socket.on(Event.ON_RECEIVED_REQUESTS, this.onReceivedRequests) socket.on(Event.ON_SENT_REQUESTS, this.onSentRequests) + socket.on(Event.ON_BIO, this.onBio) socket.on(IS_GUN_AUTH, this.isGunAuth) } @@ -889,6 +891,58 @@ class Mediator { }) } } + + /** + * @param {Readonly<{ token: string }>} body + */ + onBio = async body => { + try { + const { token } = body + + await throwOnInvalidToken(token) + + API.Events.onBio(bio => { + this.socket.emit(Event.ON_BIO, { + msg: bio, + ok: true, + origBody: body + }) + }, user) + } catch (err) { + console.log(err) + this.socket.emit(Event.ON_BIO, { + ok: false, + msg: err.message, + origBody: body + }) + } + } + + /** + * @param {Readonly<{ bio: string|null , token: string }>} body + */ + setBio = async body => { + try { + const { bio, token } = body + + await throwOnInvalidToken(token) + + await API.Actions.setBio(bio, user) + + this.socket.emit(Action.SET_BIO, { + ok: true, + msg: null, + origBody: body + }) + } catch (err) { + console.log(err) + this.socket.emit(Action.SET_BIO, { + ok: false, + msg: err.message, + origBody: body + }) + } + } } /** diff --git a/services/gunDB/action-constants.js b/services/gunDB/action-constants.js index 440e7bcf..546bd5b7 100644 --- a/services/gunDB/action-constants.js +++ b/services/gunDB/action-constants.js @@ -7,7 +7,8 @@ const Actions = { SEND_MESSAGE: "SEND_MESSAGE", SEND_PAYMENT: "SEND_PAYMENT", SET_AVATAR: "SET_AVATAR", - SET_DISPLAY_NAME: "SET_DISPLAY_NAME" + SET_DISPLAY_NAME: "SET_DISPLAY_NAME", + SET_BIO: "SET_BIO" }; module.exports = Actions; diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index b6b7de2b..bef61340 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -989,6 +989,39 @@ const generateOrderAddress = user => }) }) +/** + * @param {string|null} bio + * @param {UserGUNNode} user + * @throws {TypeError} Rejects if avatar is not an string or an empty string. + * @returns {Promise} + */ +const setBio = (bio, user) => + new Promise((resolve, reject) => { + if (!user.is) { + throw new Error(ErrorCode.NOT_AUTH) + } + + if (typeof bio === 'string' && bio.length === 0) { + throw new TypeError( + "'bio' must be an string and have length greater than one or be null" + ) + } + + if (typeof bio !== 'string' && bio !== null) { + throw new TypeError( + "'bio' must be an string and have length greater than one or be null" + ) + } + + user.get(Key.BIO).put(bio, ack => { + if (ack.err) { + reject(new Error(ack.err)) + } else { + resolve() + } + }) + }) + module.exports = { INITIAL_MSG, __createOutgoingFeed, @@ -1003,5 +1036,6 @@ module.exports = { setAvatar, setDisplayName, sendPayment, - generateOrderAddress + generateOrderAddress, + setBio } diff --git a/services/gunDB/contact-api/events.js b/services/gunDB/contact-api/events.js index 175d4fb2..6b5540ee 100644 --- a/services/gunDB/contact-api/events.js +++ b/services/gunDB/contact-api/events.js @@ -1204,6 +1204,32 @@ const onSimplerSentRequests = async (cb, gun, user, SEA) => { }) } +/** @type {string|null} */ +let currentBio = null + +/** + * @param {(bio: string|null) => void} cb + * @param {UserGUNNode} user Pass only for testing purposes. + * @throws {Error} If user hasn't been auth. + * @returns {void} + */ +const onBio = (cb, user) => { + if (!user.is) { + throw new Error(ErrorCode.NOT_AUTH) + } + + const callb = debounce(cb, DEBOUNCE_WAIT_TIME) + // Initial value if avvatar is undefined in gun + callb(currentBio) + + user.get(Key.BIO).on(bio => { + if (typeof bio === 'string' || bio === null) { + currentBio = bio + callb(bio) + } + }) +} + module.exports = { __onSentRequestToUser, __onUserToIncoming, @@ -1215,5 +1241,6 @@ module.exports = { onOutgoing, onChats, onSimplerReceivedRequests, - onSimplerSentRequests + onSimplerSentRequests, + onBio } diff --git a/services/gunDB/contact-api/key.js b/services/gunDB/contact-api/key.js index 52d7de8b..2cec102c 100644 --- a/services/gunDB/contact-api/key.js +++ b/services/gunDB/contact-api/key.js @@ -30,3 +30,5 @@ exports.CURRENT_ORDER_ADDRESS = 'currentOrderAddress' exports.ORDER_NODES = 'orderNodes' exports.ORDER_TO_RESPONSE = 'orderToResponse' + +exports.BIO = 'bio' diff --git a/services/gunDB/event-constants.js b/services/gunDB/event-constants.js index 6a910689..0c9c578f 100644 --- a/services/gunDB/event-constants.js +++ b/services/gunDB/event-constants.js @@ -5,7 +5,8 @@ const Events = { ON_DISPLAY_NAME: "ON_DISPLAY_NAME", ON_HANDSHAKE_ADDRESS: "ON_HANDSHAKE_ADDRESS", ON_RECEIVED_REQUESTS: "ON_RECEIVED_REQUESTS", - ON_SENT_REQUESTS: "ON_SENT_REQUESTS" + ON_SENT_REQUESTS: "ON_SENT_REQUESTS", + ON_BIO: "ON_BIO" }; module.exports = Events; From 06efb05830cd09b79847fd1c878ed0fddd6a6eb1 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Fri, 24 Jan 2020 16:52:58 -0400 Subject: [PATCH 26/32] export mysea --- services/gunDB/Mediator/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index ef1b3d98..32ac03d5 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -1043,5 +1043,6 @@ module.exports = { register, instantiateGun, getGun, - getUser + getUser, + mySEA } From 0a88f19f75f34f1dcb4e08c1eb52de837d6d0e52 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Fri, 24 Jan 2020 16:53:45 -0400 Subject: [PATCH 27/32] seed backup action --- services/gunDB/contact-api/actions.js | 32 ++++++++++++++++++++++++++- services/gunDB/contact-api/key.js | 2 ++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/services/gunDB/contact-api/actions.js b/services/gunDB/contact-api/actions.js index bef61340..1bb3d347 100644 --- a/services/gunDB/contact-api/actions.js +++ b/services/gunDB/contact-api/actions.js @@ -1022,6 +1022,35 @@ const setBio = (bio, user) => }) }) +/** + * @param {string[]} mnemonicPhrase + * @param {UserGUNNode} user + * @param {ISEA} SEA + * @returns {Promise} + */ +const saveSeedBackup = async (mnemonicPhrase, user, SEA) => { + if ( + !Array.isArray(mnemonicPhrase) || + mnemonicPhrase.some(word => typeof word !== 'string') || + mnemonicPhrase.length === 0 + ) { + throw new TypeError('expected mnemonicPhrase to be an string array') + } + + const mySecret = await SEA.secret(user._.sea.epub, user._.sea) + const encryptedSeed = await SEA.encrypt(mnemonicPhrase.join(' '), mySecret) + + return new Promise((res, rej) => { + user.get(Key.SEED_BACKUP).put(encryptedSeed, ack => { + if (ack.err) { + rej(ack.err) + } else { + res() + } + }) + }) +} + module.exports = { INITIAL_MSG, __createOutgoingFeed, @@ -1037,5 +1066,6 @@ module.exports = { setDisplayName, sendPayment, generateOrderAddress, - setBio + setBio, + saveSeedBackup } diff --git a/services/gunDB/contact-api/key.js b/services/gunDB/contact-api/key.js index 2cec102c..0902a454 100644 --- a/services/gunDB/contact-api/key.js +++ b/services/gunDB/contact-api/key.js @@ -32,3 +32,5 @@ exports.ORDER_NODES = 'orderNodes' exports.ORDER_TO_RESPONSE = 'orderToResponse' exports.BIO = 'bio' + +exports.SEED_BACKUP = 'seedBackup' From d75bf2708e02c62808e6c0bd82b77f38ac0bcca8 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Fri, 24 Jan 2020 17:03:05 -0400 Subject: [PATCH 28/32] save seed backup at sign up --- src/routes.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/routes.js b/src/routes.js index 143492ab..4dbfb316 100644 --- a/src/routes.js +++ b/src/routes.js @@ -14,6 +14,7 @@ const auth = require("../services/auth/auth"); const FS = require("../utils/fs"); const LightningServices = require("../utils/lightningServices"); const GunDB = require("../services/gunDB/Mediator"); +const GunActions = require("../services/gunDB/contact-api/actions") const { unprotectedRoutes } = require("../utils/protectedRoutes"); const DEFAULT_MAX_NUM_ROUTES_TO_QUERY = 10; @@ -440,7 +441,13 @@ module.exports = ( // Register user before creating wallet const publicKey = await GunDB.register(alias, password); - + + await GunActions.saveSeedBackup( + mnemonicPhrase, + GunDB.getUser(), + GunDB.mySEA + ) + walletUnlocker.initWallet( walletArgs, async (initWalletErr, initWalletResponse) => { From 6b6e7ce24010672cb6d71260b6cab522b051f8eb Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Fri, 24 Jan 2020 19:50:31 -0400 Subject: [PATCH 29/32] seed backup event --- services/gunDB/Mediator/index.js | 27 +++++++++++++++++++++++++++ services/gunDB/contact-api/events.js | 28 +++++++++++++++++++++++++++- services/gunDB/event-constants.js | 3 ++- 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 32ac03d5..2d765d67 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -309,6 +309,7 @@ class Mediator { socket.on(Event.ON_RECEIVED_REQUESTS, this.onReceivedRequests) socket.on(Event.ON_SENT_REQUESTS, this.onSentRequests) socket.on(Event.ON_BIO, this.onBio) + socket.on(Event.ON_SEED_BACKUP, this.onSeedBackup) socket.on(IS_GUN_AUTH, this.isGunAuth) } @@ -943,6 +944,32 @@ class Mediator { }) } } + + /** + * @param {Readonly<{ token: string }>} body + */ + onSeedBackup = async body => { + try { + const { token } = body + + await throwOnInvalidToken(token) + + API.Events.onSeedBackup(seedBackup => { + this.socket.emit(Event.ON_SEED_BACKUP, { + ok: true, + msg: seedBackup, + origBody: body + }) + }, user) + } catch (err) { + console.log(err) + this.socket.emit(Event.ON_SEED_BACKUP, { + ok: false, + msg: err.message, + origBody: body + }) + } + } } /** diff --git a/services/gunDB/contact-api/events.js b/services/gunDB/contact-api/events.js index 6b5540ee..3c25ae60 100644 --- a/services/gunDB/contact-api/events.js +++ b/services/gunDB/contact-api/events.js @@ -1230,6 +1230,31 @@ const onBio = (cb, user) => { }) } +/** @type {string|null} */ +let currentSeedBackup = null + +/** + * @param {(seedBackup: string|null) => void} cb + * @param {UserGUNNode} user + * @throws {Error} If user hasn't been auth. + * @returns {void} + */ +const onSeedBackup = (cb, user) => { + if (!user.is) { + throw new Error(ErrorCode.NOT_AUTH) + } + + const callb = debounce(cb, DEBOUNCE_WAIT_TIME) + callb(currentSeedBackup) + + user.get(Key.SEED_BACKUP).on(seedBackup => { + if (typeof seedBackup === 'string' || seedBackup === null) { + currentSeedBackup = seedBackup + callb(seedBackup) + } + }) +} + module.exports = { __onSentRequestToUser, __onUserToIncoming, @@ -1242,5 +1267,6 @@ module.exports = { onChats, onSimplerReceivedRequests, onSimplerSentRequests, - onBio + onBio, + onSeedBackup } diff --git a/services/gunDB/event-constants.js b/services/gunDB/event-constants.js index 0c9c578f..c4ffdae4 100644 --- a/services/gunDB/event-constants.js +++ b/services/gunDB/event-constants.js @@ -6,7 +6,8 @@ const Events = { ON_HANDSHAKE_ADDRESS: "ON_HANDSHAKE_ADDRESS", ON_RECEIVED_REQUESTS: "ON_RECEIVED_REQUESTS", ON_SENT_REQUESTS: "ON_SENT_REQUESTS", - ON_BIO: "ON_BIO" + ON_BIO: "ON_BIO", + ON_SEED_BACKUP: "ON_SEED_BACKUP", }; module.exports = Events; From 07dc27e2b40ee8828292eb287c885e1456d554e0 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Fri, 24 Jan 2020 21:28:52 -0400 Subject: [PATCH 30/32] unencrypt seed backup --- services/gunDB/contact-api/events.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/services/gunDB/contact-api/events.js b/services/gunDB/contact-api/events.js index 3c25ae60..a0c14d9f 100644 --- a/services/gunDB/contact-api/events.js +++ b/services/gunDB/contact-api/events.js @@ -1236,21 +1236,24 @@ let currentSeedBackup = null /** * @param {(seedBackup: string|null) => void} cb * @param {UserGUNNode} user + * @param {ISEA} SEA * @throws {Error} If user hasn't been auth. - * @returns {void} + * @returns {Promise} */ -const onSeedBackup = (cb, user) => { +const onSeedBackup = async (cb, user, SEA) => { if (!user.is) { throw new Error(ErrorCode.NOT_AUTH) } + const mySecret = await SEA.secret(user._.sea.epub, user._.sea) + const callb = debounce(cb, DEBOUNCE_WAIT_TIME) callb(currentSeedBackup) - user.get(Key.SEED_BACKUP).on(seedBackup => { - if (typeof seedBackup === 'string' || seedBackup === null) { - currentSeedBackup = seedBackup - callb(seedBackup) + user.get(Key.SEED_BACKUP).on(async seedBackup => { + if (typeof seedBackup === 'string') { + currentSeedBackup = await SEA.decrypt(seedBackup, mySecret) + callb(currentSeedBackup) } }) } From b64b580d45b27bb0bdb590b0526b66e01a8befa3 Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Fri, 24 Jan 2020 21:31:07 -0400 Subject: [PATCH 31/32] provide SEA --- services/gunDB/Mediator/index.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 2d765d67..51f68b68 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -954,13 +954,17 @@ class Mediator { await throwOnInvalidToken(token) - API.Events.onSeedBackup(seedBackup => { - this.socket.emit(Event.ON_SEED_BACKUP, { - ok: true, - msg: seedBackup, - origBody: body - }) - }, user) + API.Events.onSeedBackup( + seedBackup => { + this.socket.emit(Event.ON_SEED_BACKUP, { + ok: true, + msg: seedBackup, + origBody: body + }) + }, + user, + mySEA + ) } catch (err) { console.log(err) this.socket.emit(Event.ON_SEED_BACKUP, { From 407fe7e493312c72914178b0cbbb6f9e7c94741b Mon Sep 17 00:00:00 2001 From: Daniel Lugo Date: Fri, 24 Jan 2020 21:33:10 -0400 Subject: [PATCH 32/32] await --- services/gunDB/Mediator/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/gunDB/Mediator/index.js b/services/gunDB/Mediator/index.js index 51f68b68..46735819 100644 --- a/services/gunDB/Mediator/index.js +++ b/services/gunDB/Mediator/index.js @@ -954,7 +954,7 @@ class Mediator { await throwOnInvalidToken(token) - API.Events.onSeedBackup( + await API.Events.onSeedBackup( seedBackup => { this.socket.emit(Event.ON_SEED_BACKUP, { ok: true,