From d9900cd4fddf5acf1df2b0bf0efc52dad87d0870 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Tue, 30 Nov 2021 09:20:43 +0100 Subject: [PATCH 01/11] feat: configurable on-chain confirmations target confirmations target as a configurable variable instead of hardcoded const Signed-off-by: Denys Bitaccess --- class/User.js | 10 ++++++---- config.js | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/class/User.js b/class/User.js index a4f8e867..3eb888ee 100644 --- a/class/User.js +++ b/class/User.js @@ -325,18 +325,19 @@ export class User { } /** - * User's onchain txs that are >= 3 confs + * User's onchain txs that are >= configured target confirmations * Queries bitcoind RPC. * * @returns {Promise} */ async getTxs() { const addr = await this.getOrGenerateAddress(); + const targetConfirmations = config.bitcoin.confirmations; let txs = await this._listtransactions(); txs = txs.result; let result = []; for (let tx of txs) { - if (tx.confirmations >= 3 && tx.address === addr && tx.category === 'receive') { + if (tx.confirmations >= targetConfirmations && tx.address === addr && tx.category === 'receive') { tx.type = 'bitcoind_tx'; result.push(tx); } @@ -461,17 +462,18 @@ export class User { } /** - * Returning onchain txs for user's address that are less than 3 confs + * Returning onchain txs for user's address that are less than configured target confirmations * * @returns {Promise} */ async getPendingTxs() { const addr = await this.getOrGenerateAddress(); + const targetConfirmations = config.bitcoin.confirmations; let txs = await this._listtransactions(); txs = txs.result; let result = []; for (let tx of txs) { - if (tx.confirmations < 3 && tx.address === addr && tx.category === 'receive') { + if (tx.confirmations < targetConfirmations && tx.address === addr && tx.category === 'receive') { result.push(tx); } } diff --git a/config.js b/config.js index 840a265a..6f3d73c2 100644 --- a/config.js +++ b/config.js @@ -18,6 +18,9 @@ let config = { url: '1.1.1.1:10009', password: '', }, + bitcoin: { + confirmations: 3, + }, }; if (process.env.CONFIG) { From ac2a1af957f6f869685266539f8483da39e10907 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Tue, 30 Nov 2021 16:23:20 +0100 Subject: [PATCH 02/11] fix: tokens management * add tokens lifetime as configurable variable * invalidate current token during authentication (login + pass; refresh) * limit scope of tokens to access and refresh only Signed-off-by: Denys Bitaccess --- class/User.js | 84 ++++++++++++++++++++++++++++++++++++---------- config.js | 4 +++ controllers/api.js | 29 ++++++++++++++-- 3 files changed, 97 insertions(+), 20 deletions(-) diff --git a/class/User.js b/class/User.js index a4f8e867..da93b3f3 100644 --- a/class/User.js +++ b/class/User.js @@ -37,7 +37,7 @@ export class User { return this._password; } getAccessToken() { - return this._acess_token; + return this._access_token; } getRefreshToken() { return this._refresh_token; @@ -48,23 +48,34 @@ export class User { let access_token = authorization.replace('Bearer ', ''); let userid = await this._redis.get('userid_for_' + access_token); - if (userid) { - this._userid = userid; - return true; + if (!userid) { + return false } - return false; + let refresh_token = await this._redis.get('refresh_token_for_' + userid) + if (refresh_token === access_token) { + return false + } + + this._userid = userid; + return true; } async loadByRefreshToken(refresh_token) { let userid = await this._redis.get('userid_for_' + refresh_token); - if (userid) { - this._userid = userid; - await this._generateTokens(); - return true; + + if (!userid) { + return false; } - return false; + let access_token = await this._redis.get('access_token_for_' + userid) + if (access_token === refresh_token) { + return false + } + + this._userid = userid; + await this._generateTokens(); + return true; } async create() { @@ -479,16 +490,55 @@ export class User { } async _generateTokens() { - let buffer = crypto.randomBytes(20); - this._acess_token = buffer.toString('hex'); + await this._invalidateCurrentTokens(); - buffer = crypto.randomBytes(20); + await this._generateAccessToken(); + await this._generateRefreshToken(); + } + + async _invalidateCurrentTokens() { + this._access_token = await this._redis.get('access_token_for_' + this._userid); + this._refresh_token = await this._redis.get('refresh_token_for_' + this._userid); + + await this._redis.del('access_token_for_' + this._userid); + await this._redis.del('refresh_token_for_' + this._userid); + await this._redis.del('userid_for_' + this._access_token); + await this._redis.del('userid_for_' + this._refresh_token); + + this._access_token = null + this._refresh_token = null + } + + async _generateAccessToken() { + const buffer = crypto.randomBytes(20); + this._access_token = buffer.toString('hex'); + + const key_UId_AT = 'userid_for_' + this._access_token; + const key_AT_UId = 'access_token_for_' + this._userid; + + await this._redis.set(key_UId_AT, this._userid); + await this._redis.set(key_AT_UId, this._access_token); + + if (config.auth.accessTokenLifeTime) { + await this._redis.expire(key_UId_AT, config.auth.accessTokenLifeTime); + await this._redis.expire(key_AT_UId, config.auth.accessTokenLifeTime); + } + } + + async _generateRefreshToken() { + const buffer = crypto.randomBytes(20); this._refresh_token = buffer.toString('hex'); - await this._redis.set('userid_for_' + this._acess_token, this._userid); - await this._redis.set('userid_for_' + this._refresh_token, this._userid); - await this._redis.set('access_token_for_' + this._userid, this._acess_token); - await this._redis.set('refresh_token_for_' + this._userid, this._refresh_token); + const key_UId_RT = 'userid_for_' + this._refresh_token; + const key_RT_UId = 'refresh_token_for_' + this._userid; + + await this._redis.set(key_UId_RT, this._userid); + await this._redis.set(key_RT_UId, this._refresh_token); + + if (config.auth.refreshTokenLifeTime) { + await this._redis.expire(key_UId_RT, config.auth.refreshTokenLifeTime); + await this._redis.expire(key_RT_UId, config.auth.refreshTokenLifeTime); + } } async _saveUserToDatabase() { diff --git a/config.js b/config.js index 840a265a..7fa1e6d2 100644 --- a/config.js +++ b/config.js @@ -4,6 +4,10 @@ let config = { rateLimit: 200, forwardReserveFee: 0.01, // default 0.01 intraHubFee: 0.003, // default 0.003 + auth: { + accessTokenLifeTime: 3600, + refreshTokenLifeTime: 86400, + }, bitcoind: { rpc: 'http://login:password@1.1.1.1:8332/wallet/wallet.dat', }, diff --git a/controllers/api.js b/controllers/api.js index 2a582e63..8351695e 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -164,15 +164,38 @@ router.post('/auth', postLimiter, async function (req, res) { if (req.body.refresh_token) { // need to refresh token if (await u.loadByRefreshToken(req.body.refresh_token)) { - res.send({ refresh_token: u.getRefreshToken(), access_token: u.getAccessToken() }); + const body = { + refresh_token: u.getRefreshToken(), + access_token: u.getAccessToken() + } + if (config.auth.accessTokenLifeTime) { + body.access_token_expires_in = config.auth.accessTokenLifeTime + } + if (config.auth.refreshTokenLifeTime) { + body.refresh_token_expires_in = config.auth.refreshTokenLifeTime + } + res.send(body); } else { return errorBadAuth(res); } } else { // need to authorize user let result = await u.loadByLoginAndPassword(req.body.login, req.body.password); - if (result) res.send({ refresh_token: u.getRefreshToken(), access_token: u.getAccessToken() }); - else errorBadAuth(res); + if (result) { + const body = { + refresh_token: u.getRefreshToken(), + access_token: u.getAccessToken() + } + if (config.auth.accessTokenLifeTime) { + body.access_token_expires_in = config.auth.accessTokenLifeTime + } + if (config.auth.refreshTokenLifeTime) { + body.refresh_token_expires_in = config.auth.refreshTokenLifeTime + } + res.send(body); + } else { + errorBadAuth(res); + } } }); From dc0abefce7aa052dde3ac384f18e4678309b27f1 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Wed, 1 Dec 2021 07:53:48 +0100 Subject: [PATCH 03/11] use stronger crypto Signed-off-by: Denys Bitaccess --- class/User.js | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/class/User.js b/class/User.js index da93b3f3..9c32d13d 100644 --- a/class/User.js +++ b/class/User.js @@ -79,17 +79,10 @@ export class User { } async create() { - let buffer = crypto.randomBytes(10); - let login = buffer.toString('hex'); + this._login = this._generateDigest(); + this._password = this._generateDigest(); + this._userid = this._generateDigest(); - buffer = crypto.randomBytes(10); - let password = buffer.toString('hex'); - - buffer = crypto.randomBytes(24); - let userid = buffer.toString('hex'); - this._login = login; - this._password = password; - this._userid = userid; await this._saveUserToDatabase(); } @@ -510,8 +503,7 @@ export class User { } async _generateAccessToken() { - const buffer = crypto.randomBytes(20); - this._access_token = buffer.toString('hex'); + this._access_token = this._generateDigest(); const key_UId_AT = 'userid_for_' + this._access_token; const key_AT_UId = 'access_token_for_' + this._userid; @@ -526,8 +518,7 @@ export class User { } async _generateRefreshToken() { - const buffer = crypto.randomBytes(20); - this._refresh_token = buffer.toString('hex'); + this._refresh_token = this._generateDigest(); const key_UId_RT = 'userid_for_' + this._refresh_token; const key_RT_UId = 'refresh_token_for_' + this._userid; @@ -546,6 +537,11 @@ export class User { await this._redis.set((key = 'user_' + this._login + '_' + this._hash(this._password)), this._userid); } + _generateDigest() { + const buffer = crypto.randomBytes(256); + return crypto.createHash('sha1').update(buffer).digest('hex'); + } + /** * Fetches all onchain txs for user's address, and compares them to * already imported txids (stored in database); Ones that are not imported - From d09a0d6c07bcf76936a105fb32178aaca1802fa3 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Wed, 1 Dec 2021 08:11:42 +0100 Subject: [PATCH 04/11] sensible defaults for auth config Signed-off-by: Denys Bitaccess --- class/User.js | 12 ++++-------- controllers/api.js | 22 ++++++++-------------- 2 files changed, 12 insertions(+), 22 deletions(-) diff --git a/class/User.js b/class/User.js index 9c32d13d..ad013a71 100644 --- a/class/User.js +++ b/class/User.js @@ -511,10 +511,8 @@ export class User { await this._redis.set(key_UId_AT, this._userid); await this._redis.set(key_AT_UId, this._access_token); - if (config.auth.accessTokenLifeTime) { - await this._redis.expire(key_UId_AT, config.auth.accessTokenLifeTime); - await this._redis.expire(key_AT_UId, config.auth.accessTokenLifeTime); - } + await this._redis.expire(key_UId_AT, accessTokenLifeTime); + await this._redis.expire(key_AT_UId, accessTokenLifeTime); } async _generateRefreshToken() { @@ -526,10 +524,8 @@ export class User { await this._redis.set(key_UId_RT, this._userid); await this._redis.set(key_RT_UId, this._refresh_token); - if (config.auth.refreshTokenLifeTime) { - await this._redis.expire(key_UId_RT, config.auth.refreshTokenLifeTime); - await this._redis.expire(key_RT_UId, config.auth.refreshTokenLifeTime); - } + await this._redis.expire(key_UId_RT, refreshTokenLifeTime); + await this._redis.expire(key_RT_UId, refreshTokenLifeTime); } async _saveUserToDatabase() { diff --git a/controllers/api.js b/controllers/api.js index 8351695e..d239b044 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -21,6 +21,8 @@ redis.monitor(function (err, monitor) { /** GLOBALS */ global.forwardFee = config.forwardReserveFee || 0.01; global.internalFee = config.intraHubFee || 0.003; +global.accessTokenLifeTime = config.auth?.accessTokenLifeTime || 3600; +global.refreshTokenLifeTime = config.auth?.refreshTokenLifeTime || 86400; /****** END SET FEES FROM CONFIG AT STARTUP ******/ let bitcoinclient = require('../bitcoin'); @@ -165,14 +167,10 @@ router.post('/auth', postLimiter, async function (req, res) { // need to refresh token if (await u.loadByRefreshToken(req.body.refresh_token)) { const body = { + token_type: 'bearer', refresh_token: u.getRefreshToken(), - access_token: u.getAccessToken() - } - if (config.auth.accessTokenLifeTime) { - body.access_token_expires_in = config.auth.accessTokenLifeTime - } - if (config.auth.refreshTokenLifeTime) { - body.refresh_token_expires_in = config.auth.refreshTokenLifeTime + access_token: u.getAccessToken(), + expires_in: accessTokenLifeTime, } res.send(body); } else { @@ -183,14 +181,10 @@ router.post('/auth', postLimiter, async function (req, res) { let result = await u.loadByLoginAndPassword(req.body.login, req.body.password); if (result) { const body = { + token_type: 'bearer', refresh_token: u.getRefreshToken(), - access_token: u.getAccessToken() - } - if (config.auth.accessTokenLifeTime) { - body.access_token_expires_in = config.auth.accessTokenLifeTime - } - if (config.auth.refreshTokenLifeTime) { - body.refresh_token_expires_in = config.auth.refreshTokenLifeTime + access_token: u.getAccessToken(), + expires_in: accessTokenLifeTime } res.send(body); } else { From 3ca5c2afc850e1e6f4878d9823808e3a25c5ff0a Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Wed, 1 Dec 2021 08:19:55 +0100 Subject: [PATCH 05/11] refactor auth endpoint Signed-off-by: Denys Bitaccess --- controllers/api.js | 46 ++++++++++++++++++---------------------------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/controllers/api.js b/controllers/api.js index d239b044..2ff447a1 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -159,38 +159,28 @@ router.post('/create', postLimiter, async function (req, res) { router.post('/auth', postLimiter, async function (req, res) { logger.log('/auth', [req.id]); - if (!((req.body.login && req.body.password) || req.body.refresh_token)) return errorBadArguments(res); - let u = new User(redis, bitcoinclient, lightning); + const u = new User(redis, bitcoinclient, lightning); - if (req.body.refresh_token) { - // need to refresh token - if (await u.loadByRefreshToken(req.body.refresh_token)) { - const body = { - token_type: 'bearer', - refresh_token: u.getRefreshToken(), - access_token: u.getAccessToken(), - expires_in: accessTokenLifeTime, - } - res.send(body); - } else { - return errorBadAuth(res); - } + let authenticated = false + if (req.body.login && req.body.password) { + authenticated = await u.loadByLoginAndPassword(req.body.login, req.body.password); + } else if (req.body.refresh_token) { + authenticated = await u.loadByRefreshToken(req.body.refresh_token) } else { - // need to authorize user - let result = await u.loadByLoginAndPassword(req.body.login, req.body.password); - if (result) { - const body = { - token_type: 'bearer', - refresh_token: u.getRefreshToken(), - access_token: u.getAccessToken(), - expires_in: accessTokenLifeTime - } - res.send(body); - } else { - errorBadAuth(res); - } + return errorBadArguments(res); + } + + if (!authenticated) { + return errorBadAuth(res); } + + return res.send({ + token_type: 'bearer', + refresh_token: u.getRefreshToken(), + access_token: u.getAccessToken(), + expires_in: accessTokenLifeTime, + }); }); router.post('/addinvoice', postLimiter, async function (req, res) { From f62af6ffd27b3f61afd00990fd78782d26b9c2a7 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Wed, 1 Dec 2021 13:38:10 +0100 Subject: [PATCH 06/11] extended API: * get invoice by hash Signed-off-by: Denys Bitaccess --- class/User.js | 5 +++++ controllers/api.js | 20 ++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/class/User.js b/class/User.js index 3eb888ee..13611815 100644 --- a/class/User.js +++ b/class/User.js @@ -320,6 +320,11 @@ export class User { return result; } + async getUserInvoiceByHash(hash) { + const invoices = await this.getUserInvoices(); + return invoices.find(i => Buffer.from(i['r_hash']).toString('hex') === hash); + } + async addAddress(address) { await this._redis.set('bitcoin_address_for_' + this._userid, address); } diff --git a/controllers/api.js b/controllers/api.js index 2a582e63..08631fe6 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -444,6 +444,26 @@ router.get('/gettxs', async function (req, res) { } }); +router.get('/getuserinvoices/:invoice_hash', postLimiter, async function (req, res) { + const invoiceHash = req.params.invoice_hash; + logger.log('/getuserinvoices/' + invoiceHash, [req.id]); + + let u = new User(redis, bitcoinclient, lightning); + if (!(await u.loadByAuthorization(req.headers.authorization))) { + return errorBadAuth(res); + } + + logger.log('/getuserinvoices'/ + invoiceHash, [req.id, 'userid: ' + u.getUserId()]); + + try { + const invoice = await u.getUserInvoiceByHash(invoiceHash) + res.send(invoice || {}) + } catch (Err) { + logger.log('', [req.id, 'error getting user invoice ' + invoiceHash + ':', Err.message, 'userid:', u.getUserId()]); + res.send({}); + } +}); + router.get('/getuserinvoices', postLimiter, async function (req, res) { logger.log('/getuserinvoices', [req.id]); let u = new User(redis, bitcoinclient, lightning); From fa7af0d80c10d238a0196be5c45a0ac3e19a200c Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Wed, 1 Dec 2021 14:34:29 +0100 Subject: [PATCH 07/11] extended API * extend onchain tx data * extent lightning tx data * refactor tx lookup * add tx by hash lookup Signed-off-by: Denys Bitaccess --- class/User.js | 39 ++++++++++++++++++++++++++++++++++++--- controllers/api.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/class/User.js b/class/User.js index 13611815..d0627a5e 100644 --- a/class/User.js +++ b/class/User.js @@ -322,7 +322,7 @@ export class User { async getUserInvoiceByHash(hash) { const invoices = await this.getUserInvoices(); - return invoices.find(i => Buffer.from(i['r_hash']).toString('hex') === hash); + return invoices.find(i => Buffer.from(i.r_hash).toString('hex') === hash); } async addAddress(address) { @@ -336,6 +336,13 @@ export class User { * @returns {Promise} */ async getTxs() { + let onchainTx = await this.getOnchainTxs() + let lightningTxs = await this.getLightningTxs() + + return [...onchainTx, ...lightningTxs] + } + + async getOnchainTxs() { const addr = await this.getOrGenerateAddress(); const targetConfirmations = config.bitcoin.confirmations; let txs = await this._listtransactions(); @@ -348,6 +355,16 @@ export class User { } } + return result + } + + async getOnchainTxById(id) { + const txs = await this.getOnchainTxs(); + return txs.find(tx => id === `${tx.txid}${tx.vout}`); + } + + async getLightningTxs() { + const result = [] let range = await this._redis.lrange('txs_for_' + this._userid, 0, -1); for (let invoice of range) { invoice = JSON.parse(invoice); @@ -374,17 +391,25 @@ export class User { if (invoice.payment_preimage) { invoice.payment_preimage = Buffer.from(invoice.payment_preimage, 'hex').toString('hex'); } + let hash = lightningPayReq.decode(invoice.pay_req).tags.find(t => t.tagName === 'payment_hash') + invoice.r_hash = Buffer.from(hash.data, 'hex') // removing unsued by client fields to reduce size delete invoice.payment_error; delete invoice.payment_route; delete invoice.pay_req; delete invoice.decoded; + result.push(invoice); } return result; } + async getLightningTxByHash(hash) { + const txs = await this.getLightningTxs() + return txs.find(tx => Buffer.from(tx.r_hash).toString('hex') === hash); + } + /** * Simple caching for this._bitcoindrpc.request('listtransactions', ['*', 100500, 0, true]); * since its too much to fetch from bitcoind every time @@ -414,8 +439,11 @@ export class User { // now, compacting response a bit for (const tx of txs.result) { ret.result.push({ + txid: tx.txid, + vout: tx.vout, category: tx.category, amount: tx.amount, + fee: tx.fee, confirmations: tx.confirmations, address: tx.address, time: tx.blocktime || tx.time, @@ -449,12 +477,15 @@ export class User { .filter((tx) => tx.label !== 'external' && !tx.label.includes('openchannel')) .map((tx) => { const decodedTx = decodeRawHex(tx.raw_tx_hex); - decodedTx.outputs.forEach((vout) => + decodedTx.outputs.forEach((vout, i) => outTxns.push({ // mark all as received, since external is filtered out + txid: tx.hash, + vout: i, category: 'receive', - confirmations: tx.num_confirmations, amount: Number(vout.value), + fee: Math.ceil(decodedTx.fees / decodedTx.vout_sz), + confirmations: tx.num_confirmations, address: vout.scriptPubKey.addresses[0], time: tx.time_stamp, }), @@ -554,6 +585,8 @@ export class User { amount: +decodedInvoice.num_satoshis, timestamp: Math.floor(+new Date() / 1000), }; + const hash = lightningPayReq.decode(pay_req).tags.find(t => t.tagName === 'payment_hash') + doc.r_hash = Buffer.from(hash.data, 'hex') return this._redis.rpush('locked_payments_for_' + this._userid, JSON.stringify(doc)); } diff --git a/controllers/api.js b/controllers/api.js index 08631fe6..eee8bdbc 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -415,6 +415,50 @@ router.get('/getinfo', postLimiter, async function (req, res) { }); }); +router.get('/gettxs/:tx_hash', async function (req, res) { + const txHash = req.params.tx_hash; + + logger.log('/gettxs/' + txHash, [req.id]); + let u = new User(redis, bitcoinclient, lightning); + if (!(await u.loadByAuthorization(req.headers.authorization))) { + return errorBadAuth(res); + } + logger.log('/gettxs/' + txHash, [req.id, 'userid: ' + u.getUserId()]); + + if (!(await u.getAddress())) await u.generateAddress(); // onchain addr needed further + try { + await u.accountForPosibleTxids(); + + let tx = await u.getLightningTxByHash(txHash) + if (tx) { + return res.send(tx) + } + + let lockedPayments = await u.getLockedPayments(); + tx = lockedPayments.find(tx => Buffer.from(tx.r_hash).toString('hex') === hash) + if (tx) { + return res.send({ + type: 'paid_invoice', + fee: Math.floor(tx.amount * forwardFee) /* feelimit */, + value: tx.amount + Math.floor(tx.amount * forwardFee) /* feelimit */, + timestamp: tx.timestamp, + memo: 'Payment in transition', + r_hash: tx.r_hash + }) + } + + tx = await u.getOnchainTxById(txHash); + if (tx) { + return res.send(tx) + } + + res.send({}); + } catch (Err) { + logger.log('', [req.id, 'error gettxs:', Err.message, 'userid:', u.getUserId()]); + res.send({}); + } +}); + router.get('/gettxs', async function (req, res) { logger.log('/gettxs', [req.id]); let u = new User(redis, bitcoinclient, lightning); @@ -435,6 +479,7 @@ router.get('/gettxs', async function (req, res) { value: locked.amount + Math.floor(locked.amount * forwardFee) /* feelimit */, timestamp: locked.timestamp, memo: 'Payment in transition', + r_hash: locked.r_hash, }); } res.send(txs); From 9a1bb331c6a143c4c0fc7ce33177f8cdaa069873 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Thu, 2 Dec 2021 22:07:04 +0100 Subject: [PATCH 08/11] move routes handling logic to middleware Signed-off-by: Denys Bitaccess --- controllers/api.js | 487 +++++++++++++++++++++++---------------------- 1 file changed, 249 insertions(+), 238 deletions(-) diff --git a/controllers/api.js b/controllers/api.js index 2a582e63..d8c3e4fa 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -131,62 +131,121 @@ if (config.enableUpdateDescribeGraph) { setInterval(updateDescribeGraph, 120000); } -// ######################## ROUTES ######################## - +// ################# MIDDLEWARE ######################## const rateLimit = require('express-rate-limit'); const postLimiter = rateLimit({ windowMs: 30 * 60 * 1000, max: config.postRateLimit || 100, }); -router.post('/create', postLimiter, async function (req, res) { - logger.log('/create', [req.id]); +function setUser(req, res, next) { + req.locals = req.locals || {} + logger.log(req.originalUrl, [req.id]); + req.locals.user = new User(redis, bitcoinclient, lightning); + + next() +} +async function authenticator(req, res, next) { + if (!(await req.locals.user.loadByAuthorization(req.headers.authorization))) { + return errorBadAuth(res); + } + next() +} + +function validateCreate(req, res, next) { // Valid if the partnerid isn't there or is a string (same with accounttype) - if (! ( - (!req.body.partnerid || (typeof req.body.partnerid === 'string' || req.body.partnerid instanceof String)) - && (!req.body.accounttype || (typeof req.body.accounttype === 'string' || req.body.accounttype instanceof String)) - ) ) return errorBadArguments(res); - - if (config.sunset) return errorSunset(res); - - let u = new User(redis, bitcoinclient, lightning); - await u.create(); - await u.saveMetadata({ partnerid: req.body.partnerid, accounttype: req.body.accounttype, created_at: new Date().toISOString() }); - res.send({ login: u.getLogin(), password: u.getPassword() }); -}); + if (!(!req.body.partnerid || (typeof req.body.partnerid === 'string' || req.body.partnerid instanceof String)) + && (!req.body.accounttype || (typeof req.body.accounttype === 'string' || req.body.accounttype instanceof String))) { + return errorBadArguments(res); + } + + return next() +} -router.post('/auth', postLimiter, async function (req, res) { - logger.log('/auth', [req.id]); - if (!((req.body.login && req.body.password) || req.body.refresh_token)) return errorBadArguments(res); +function validateAuth(req, res, next) { + if (!((req.body.login && req.body.password) || req.body.refresh_token)) { + return errorBadArguments(res); + } - let u = new User(redis, bitcoinclient, lightning); + next() +} - if (req.body.refresh_token) { - // need to refresh token - if (await u.loadByRefreshToken(req.body.refresh_token)) { - res.send({ refresh_token: u.getRefreshToken(), access_token: u.getAccessToken() }); - } else { - return errorBadAuth(res); - } - } else { - // need to authorize user - let result = await u.loadByLoginAndPassword(req.body.login, req.body.password); - if (result) res.send({ refresh_token: u.getRefreshToken(), access_token: u.getAccessToken() }); - else errorBadAuth(res); +async function authenticateByRefreshToken(req, res, next) { + if (!req.body.refresh_token) { + return next() } -}); -router.post('/addinvoice', postLimiter, async function (req, res) { - logger.log('/addinvoice', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - if (!(await u.loadByAuthorization(req.headers.authorization))) { + if (!(await req.locals.user.loadByRefreshToken(req.body.refresh_token))) { return errorBadAuth(res); } - logger.log('/addinvoice', [req.id, 'userid: ' + u.getUserId()]); + return res.send({ refresh_token: req.locals.user.getRefreshToken(), access_token: req.locals.user.getAccessToken() }); +} + +async function authenticateByPassword(req, res) { + let result = await req.locals.user.loadByLoginAndPassword(req.body.login, req.body.password); + if (!result) { + return errorBadAuth(res); + } + return res.send({ + refresh_token: req.locals.user.getRefreshToken(), + access_token: req.locals.user.getAccessToken() + }); +} + +function validateSunset(req, res, next) { + if (!config.sunset) { + return next() + } + + errorSunset(res); +} +function validateAddInvoice(req, res, next) { if (!req.body.amt || /*stupid NaN*/ !(req.body.amt > 0)) return errorBadArguments(res); - if (config.sunset) return errorSunsetAddInvoice(res); + next() +} + +function validatePayInvoice(req, res, next) { + if (!req.body.invoice) return errorBadArguments(res); + req.locals.freeAmount = false; + if (req.body.amount) { + req.locals.freeAmount = parseInt(req.body.amount); + if (req.locals.freeAmount <= 0) return errorBadArguments(res); + } + + next() +} + +async function validateDecodeInvocie(req, res, next) { + if (!req.query.invoice) { + return errorGeneralServerError(res); + } + + next() +} + +async function saveUser(req, res, next) { + await req.locals.user.create(); + await req.locals.user.saveMetadata({ + partnerid: req.body.partnerid, + accounttype: req.body.accounttype, + created_at: new Date().toISOString() + }); + + next() +} + +function sendCredentials(req, res) { + res.send({ + login: req.locals.user.getLogin(), + password: req.locals.user.getPassword() + }); +} + + +async function sendInvoice(req, res) { + logger.log('/addinvoice', [req.id, 'userid: ' + req.locals.user.getUserId(), 'amount: ' + req.body.amount]); const invoice = new Invo(redis, bitcoinclient, lightning); const r_preimage = invoice.makePreimageHex(); @@ -196,38 +255,149 @@ router.post('/addinvoice', postLimiter, async function (req, res) { if (err) return errorLnd(res); info.pay_req = info.payment_request; // client backwards compatibility - await u.saveUserInvoice(info); + await req.locals.user.saveUserInvoice(info); await invoice.savePreimage(r_preimage); res.send(info); }, ); -}); +} -router.post('/payinvoice', async function (req, res) { - let u = new User(redis, bitcoinclient, lightning); - if (!(await u.loadByAuthorization(req.headers.authorization))) { - return errorBadAuth(res); +// FIXME +async function watchAddressAndSend(req, res) { + let address = await req.locals.user.getAddress(); + req.locals.user.watchAddress(address); + + res.send([{ address }]); +} + +async function getAddress(req, res, next) { + if (!(await req.locals.user.getAddress())) { + await req.locals.user.generateAddress(); // onchain address needed further + } + + next() +} + +async function sendPaymentCheck(req, res) { + let paid = true; + if (!(await req.locals.user.getPaymentHashPaid(req.params.payment_hash))) { + // Not found on cache + paid = await req.locals.user.syncInvoicePaid(req.params.payment_hash); } + res.send({ paid: paid }); +} - logger.log('/payinvoice', [req.id, 'userid: ' + u.getUserId(), 'invoice: ' + req.body.invoice]); +function sendLightningInfo(req, res) { + lightning.getInfo({}, function (err, info) { + if (err) return errorLnd(res); + res.send(info); + }); +} - if (!req.body.invoice) return errorBadArguments(res); - let freeAmount = false; - if (req.body.amount) { - freeAmount = parseInt(req.body.amount); - if (freeAmount <= 0) return errorBadArguments(res); +async function sendBalance(req, res) { + logger.log('/balance', [req.id, 'userid: ' + req.locals.user.getUserId()]); + try { + let balance = await req.locals.user.getBalance(); + res.send({ BTC: { AvailableBalance: ((balance > 0) ? balance : 0) } }); + } catch (Error) { + logger.log('', [req.id, 'error getting balance:', Error, 'userid:', req.locals.user.getUserId()]); + return errorGeneralServerError(res); + } +} + +async function refreshTxList(req, res, next) { + await req.locals.user.accountForPosibleTxids(); + + next() +} + +async function sendPendingTxs(req, res) { + logger.log('/getpending', [req.id, 'userid: ' + req.locals.user.getUserId()]); + + let txs = await req.locals.user.getPendingTxs(); + res.send(txs); +} + +function sendDecodedInvoice(req, res) { + lightning.decodePayReq({ pay_req: req.query.invoice }, function (err, info) { + if (err) return errorNotAValidInvoice(res); + res.send(info); + }); +} + +function sendRoutes(req, res) { + logger.log('/queryroutes', [req.id]); + + let request = { + pub_key: req.params.dest, + use_mission_control: true, + amt: req.params.amt, + source_pub_key: req.params.source, + }; + lightning.queryRsendRoutes(request, function (err, response) { + console.log(JSON.stringify(response, null, 2)); + res.send(response); + }); +} + +function sendChainInfo(req, res) { + logger.log('/getchaninfo', [req.id]); + + if (lightningDescribeGraph && lightningDescribeGraph.edges) { + for (const edge of lightningDescribeGraph.edges) { + if (edge.channel_id == req.params.chanid) { + return res.send(JSON.stringify(edge, null, 2)); + } + } + } + res.send(''); +} + +async function getUserInvoicesAndSend(req, res) { + logger.log('/getuserinvoices', [req.id, 'userid: ' + req.locals.user.getUserId()]); + + try { + let invoices = await req.locals.user.getUserInvoices(req.query.limit); + res.send(invoices); + } catch (Err) { + logger.log('', [req.id, 'error getting user invoices:', Err.message, 'userid:', req.locals.user.getUserId()]); + res.send([]); + } +} + +async function getUserTxsAndSend(req, res) { + try { + let txs = await req.locals.user.getTxs(); + let lockedPayments = await req.locals.user.getLockedPayments(); + for (let locked of lockedPayments) { + txs.push({ + type: 'paid_invoice', + fee: Math.floor(locked.amount * forwardFee) /* feelimit */, + value: locked.amount + Math.floor(locked.amount * forwardFee) /* feelimit */, + timestamp: locked.timestamp, + memo: 'Payment in transition', + }); + } + res.send(txs); + } catch (Err) { + logger.log('', [req.id, 'error gettxs:', Err.message, 'userid:', req.locals.user.getUserId()]); + res.send([]); } +} + +async function payInvoiceAndSend(req, res) { + logger.log('/payinvoice', [req.id, 'userid: ' + req.locals.user.getUserId(), 'invoice: ' + req.body.invoice]); // obtaining a lock - let lock = new Lock(redis, 'invoice_paying_for_' + u.getUserId()); + let lock = new Lock(redis, 'invoice_paying_for_' + req.locals.user.getUserId()); if (!(await lock.obtainLock())) { return errorGeneralServerError(res); } let userBalance; try { - userBalance = await u.getCalculatedBalance(); + userBalance = await req.locals.user.getCalculatedBalance(); } catch (Error) { logger.log('', [req.id, 'error running getCalculatedBalance():', Error.message]); lock.releaseLock(); @@ -242,7 +412,7 @@ router.post('/payinvoice', async function (req, res) { if (+info.num_satoshis === 0) { // 'tip' invoices - info.num_satoshis = freeAmount; + info.num_satoshis = req.locals.freeAmount; } logger.log('/payinvoice', [req.id, 'userBalance: ' + userBalance, 'num_satoshis: ' + info.num_satoshis]); @@ -253,13 +423,13 @@ router.post('/payinvoice', async function (req, res) { if (identity_pubkey === info.destination) { // this is internal invoice // now, receiver add balance - let userid_payee = await u.getUseridByPaymentHash(info.payment_hash); + let userid_payee = await req.locals.user.getUseridByPaymentHash(info.payment_hash); if (!userid_payee) { await lock.releaseLock(); return errorGeneralServerError(res); } - if (await u.getPaymentHashPaid(info.payment_hash)) { + if (await req.locals.user.getPaymentHashPaid(info.payment_hash)) { // this internal invoice was paid, no sense paying it again await lock.releaseLock(); return errorLnd(res); @@ -270,8 +440,8 @@ router.post('/payinvoice', async function (req, res) { await UserPayee.clearBalanceCache(); // sender spent his balance: - await u.clearBalanceCache(); - await u.savePaidLndInvoice({ + await req.locals.user.clearBalanceCache(); + await req.locals.user.savePaidLndInvoice({ timestamp: parseInt(+new Date() / 1000), type: 'paid_invoice', value: +info.num_satoshis + Math.floor(info.num_satoshis * internalFee), @@ -304,14 +474,14 @@ router.post('/payinvoice', async function (req, res) { var call = lightning.sendPayment(); call.on('data', async function (payment) { // payment callback - await u.unlockFunds(req.body.invoice); + await req.locals.user.unlockFunds(req.body.invoice); if (payment && payment.payment_route && payment.payment_route.total_amt_msat) { let PaymentShallow = new Paym(false, false, false); payment = PaymentShallow.processSendPaymentResponse(payment); payment.pay_req = req.body.invoice; payment.decoded = info; - await u.savePaidLndInvoice(payment); - await u.clearBalanceCache(); + await req.locals.user.savePaidLndInvoice(payment); + await req.locals.user.clearBalanceCache(); lock.releaseLock(); res.send(payment); } else { @@ -331,7 +501,7 @@ router.post('/payinvoice', async function (req, res) { fee_limit: { fixed: Math.floor(info.num_satoshis * forwardFee) + 1 }, }; try { - await u.lockFunds(req.body.invoice, info); + await req.locals.user.lockFunds(req.body.invoice, info); call.write(inv); } catch (Err) { await lock.releaseLock(); @@ -342,198 +512,39 @@ router.post('/payinvoice', async function (req, res) { return errorNotEnougBalance(res); } }); -}); - -router.get('/getbtc', async function (req, res) { - logger.log('/getbtc', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - await u.loadByAuthorization(req.headers.authorization); - - if (!u.getUserId()) { - return errorBadAuth(res); - } - - if (config.sunset) return errorSunsetAddInvoice(res); - - let address = await u.getAddress(); - if (!address) { - await u.generateAddress(); - address = await u.getAddress(); - } - u.watchAddress(address); - - res.send([{ address }]); -}); - -router.get('/checkpayment/:payment_hash', async function (req, res) { - logger.log('/checkpayment', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - await u.loadByAuthorization(req.headers.authorization); - - if (!u.getUserId()) { - return errorBadAuth(res); - } - - let paid = true; - if (!(await u.getPaymentHashPaid(req.params.payment_hash))) { - // Not found on cache - paid = await u.syncInvoicePaid(req.params.payment_hash); - } - res.send({ paid: paid }); -}); - -router.get('/balance', postLimiter, async function (req, res) { - let u = new User(redis, bitcoinclient, lightning); - try { - logger.log('/balance', [req.id]); - if (!(await u.loadByAuthorization(req.headers.authorization))) { - return errorBadAuth(res); - } - logger.log('/balance', [req.id, 'userid: ' + u.getUserId()]); - - if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further - await u.accountForPosibleTxids(); - let balance = await u.getBalance(); - if (balance < 0) balance = 0; - res.send({ BTC: { AvailableBalance: balance } }); - } catch (Error) { - logger.log('', [req.id, 'error getting balance:', Error, 'userid:', u.getUserId()]); - return errorGeneralServerError(res); - } -}); - -router.get('/getinfo', postLimiter, async function (req, res) { - logger.log('/getinfo', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - if (!(await u.loadByAuthorization(req.headers.authorization))) { - return errorBadAuth(res); - } - - lightning.getInfo({}, function (err, info) { - if (err) return errorLnd(res); - res.send(info); - }); -}); +} -router.get('/gettxs', async function (req, res) { - logger.log('/gettxs', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - if (!(await u.loadByAuthorization(req.headers.authorization))) { - return errorBadAuth(res); - } - logger.log('/gettxs', [req.id, 'userid: ' + u.getUserId()]); - if (!(await u.getAddress())) await u.generateAddress(); // onchain addr needed further - try { - await u.accountForPosibleTxids(); - let txs = await u.getTxs(); - let lockedPayments = await u.getLockedPayments(); - for (let locked of lockedPayments) { - txs.push({ - type: 'paid_invoice', - fee: Math.floor(locked.amount * forwardFee) /* feelimit */, - value: locked.amount + Math.floor(locked.amount * forwardFee) /* feelimit */, - timestamp: locked.timestamp, - memo: 'Payment in transition', - }); - } - res.send(txs); - } catch (Err) { - logger.log('', [req.id, 'error gettxs:', Err.message, 'userid:', u.getUserId()]); - res.send([]); - } -}); +// ######################## ROUTES ######################## +router.post('/create', setUser, postLimiter, validateCreate, validateSunset, saveUser, sendCredentials); -router.get('/getuserinvoices', postLimiter, async function (req, res) { - logger.log('/getuserinvoices', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - if (!(await u.loadByAuthorization(req.headers.authorization))) { - return errorBadAuth(res); - } - logger.log('/getuserinvoices', [req.id, 'userid: ' + u.getUserId()]); +router.post('/auth', setUser, postLimiter, validateAuth, authenticateByRefreshToken, authenticateByPassword); - try { - let invoices = await u.getUserInvoices(req.query.limit); - res.send(invoices); - } catch (Err) { - logger.log('', [req.id, 'error getting user invoices:', Err.message, 'userid:', u.getUserId()]); - res.send([]); - } -}); +router.post('/addinvoice', setUser, postLimiter, authenticator, validateAddInvoice, validateSunset, sendInvoice); -router.get('/getpending', async function (req, res) { - logger.log('/getpending', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - if (!(await u.loadByAuthorization(req.headers.authorization))) { - return errorBadAuth(res); - } - logger.log('/getpending', [req.id, 'userid: ' + u.getUserId()]); +router.post('/payinvoice', setUser, postLimiter, authenticator, validatePayInvoice, payInvoiceAndSend); - if (!(await u.getAddress())) await u.generateAddress(); // onchain address needed further - await u.accountForPosibleTxids(); - let txs = await u.getPendingTxs(); - res.send(txs); -}); +router.get('/getbtc', setUser, authenticator, validateSunset, getAddress, watchAddressAndSend); -router.get('/decodeinvoice', async function (req, res) { - logger.log('/decodeinvoice', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - if (!(await u.loadByAuthorization(req.headers.authorization))) { - return errorBadAuth(res); - } +router.get('/checkpayment/:payment_hash', setUser, authenticator, sendPaymentCheck); - if (!req.query.invoice) return errorGeneralServerError(res); +router.get('/balance', setUser, postLimiter, authenticator, getAddress, refreshTxList, sendBalance); - lightning.decodePayReq({ pay_req: req.query.invoice }, function (err, info) { - if (err) return errorNotAValidInvoice(res); - res.send(info); - }); -}); +router.get('/getinfo', setUser, postLimiter, authenticator, sendLightningInfo); -router.get('/checkrouteinvoice', async function (req, res) { - logger.log('/checkrouteinvoice', [req.id]); - let u = new User(redis, bitcoinclient, lightning); - if (!(await u.loadByAuthorization(req.headers.authorization))) { - return errorBadAuth(res); - } +router.get('/gettxs', setUser, authenticator, getAddress, refreshTxList, getUserTxsAndSend); - if (!req.query.invoice) return errorGeneralServerError(res); +router.get('/getuserinvoices', setUser, postLimiter, authenticator, getUserInvoicesAndSend) - // at the momment does nothing. - // TODO: decode and query actual route to destination - lightning.decodePayReq({ pay_req: req.query.invoice }, function (err, info) { - if (err) return errorNotAValidInvoice(res); - res.send(info); - }); -}); +router.get('/getpending', setUser, authenticator, getAddress, refreshTxList, sendPendingTxs); -router.get('/queryroutes/:source/:dest/:amt', async function (req, res) { - logger.log('/queryroutes', [req.id]); +router.get('/decodeinvoice', setUser, authenticator, validateDecodeInvocie, sendDecodedInvoice); - let request = { - pub_key: req.params.dest, - use_mission_control: true, - amt: req.params.amt, - source_pub_key: req.params.source, - }; - lightning.queryRoutes(request, function (err, response) { - console.log(JSON.stringify(response, null, 2)); - res.send(response); - }); -}); +router.get('/checkrouteinvoice', setUser, authenticator, validateDecodeInvocie, sendDecodedInvoice); -router.get('/getchaninfo/:chanid', async function (req, res) { - logger.log('/getchaninfo', [req.id]); +router.get('/queryroutes/:source/:dest/:amt', setUser, authenticator, sendRoutes); - if (lightningDescribeGraph && lightningDescribeGraph.edges) { - for (const edge of lightningDescribeGraph.edges) { - if (edge.channel_id == req.params.chanid) { - return res.send(JSON.stringify(edge, null, 2)); - } - } - } - res.send(''); -}); +router.get('/getchaninfo/:chanid', setUser, authenticator, sendChainInfo); module.exports = router; From c49813b59d89ec528ff32952b5f077191457f5c9 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Fri, 3 Dec 2021 09:44:59 +0100 Subject: [PATCH 09/11] add ratelimiter to all andpoints Signed-off-by: Denys Bitaccess --- controllers/api.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/controllers/api.js b/controllers/api.js index d8c3e4fa..52374a9f 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -524,27 +524,27 @@ router.post('/addinvoice', setUser, postLimiter, authenticator, validateAddInvoi router.post('/payinvoice', setUser, postLimiter, authenticator, validatePayInvoice, payInvoiceAndSend); -router.get('/getbtc', setUser, authenticator, validateSunset, getAddress, watchAddressAndSend); +router.get('/getbtc', setUser, postLimiter, authenticator, validateSunset, getAddress, watchAddressAndSend); -router.get('/checkpayment/:payment_hash', setUser, authenticator, sendPaymentCheck); +router.get('/checkpayment/:payment_hash', setUser, postLimiter, authenticator, sendPaymentCheck); router.get('/balance', setUser, postLimiter, authenticator, getAddress, refreshTxList, sendBalance); router.get('/getinfo', setUser, postLimiter, authenticator, sendLightningInfo); -router.get('/gettxs', setUser, authenticator, getAddress, refreshTxList, getUserTxsAndSend); +router.get('/gettxs', setUser, postLimiter,authenticator, getAddress, refreshTxList, getUserTxsAndSend); router.get('/getuserinvoices', setUser, postLimiter, authenticator, getUserInvoicesAndSend) -router.get('/getpending', setUser, authenticator, getAddress, refreshTxList, sendPendingTxs); +router.get('/getpending', setUser, postLimiter, authenticator, getAddress, refreshTxList, sendPendingTxs); -router.get('/decodeinvoice', setUser, authenticator, validateDecodeInvocie, sendDecodedInvoice); +router.get('/decodeinvoice', setUser, postLimiter, authenticator, validateDecodeInvocie, sendDecodedInvoice); -router.get('/checkrouteinvoice', setUser, authenticator, validateDecodeInvocie, sendDecodedInvoice); +router.get('/checkrouteinvoice', setUser, postLimiter, authenticator, validateDecodeInvocie, sendDecodedInvoice); -router.get('/queryroutes/:source/:dest/:amt', setUser, authenticator, sendRoutes); +router.get('/queryroutes/:source/:dest/:amt', setUser, postLimiter, authenticator, sendRoutes); -router.get('/getchaninfo/:chanid', setUser, authenticator, sendChainInfo); +router.get('/getchaninfo/:chanid', setUser, postLimiter, authenticator, sendChainInfo); module.exports = router; From 59737f767bbda2acafdd4c96776369b6d4344ae9 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Fri, 3 Dec 2021 10:04:31 +0100 Subject: [PATCH 10/11] mass-assigned middleware Signed-off-by: Denys Bitaccess --- controllers/api.js | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/controllers/api.js b/controllers/api.js index 52374a9f..e30a19e2 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -516,35 +516,40 @@ async function payInvoiceAndSend(req, res) { // ######################## ROUTES ######################## -router.post('/create', setUser, postLimiter, validateCreate, validateSunset, saveUser, sendCredentials); +router.use(postLimiter) +router.use(setUser) -router.post('/auth', setUser, postLimiter, validateAuth, authenticateByRefreshToken, authenticateByPassword); +router.post('/create', validateCreate, validateSunset, saveUser, sendCredentials); -router.post('/addinvoice', setUser, postLimiter, authenticator, validateAddInvoice, validateSunset, sendInvoice); +router.post('/auth', validateAuth, authenticateByRefreshToken, authenticateByPassword); -router.post('/payinvoice', setUser, postLimiter, authenticator, validatePayInvoice, payInvoiceAndSend); +router.all(/^(?!(\/create|\/auth)).+$/, authenticator) -router.get('/getbtc', setUser, postLimiter, authenticator, validateSunset, getAddress, watchAddressAndSend); +router.post('/addinvoice', validateAddInvoice, validateSunset, sendInvoice); -router.get('/checkpayment/:payment_hash', setUser, postLimiter, authenticator, sendPaymentCheck); +router.post('/payinvoice', validatePayInvoice, payInvoiceAndSend); -router.get('/balance', setUser, postLimiter, authenticator, getAddress, refreshTxList, sendBalance); +router.get('/getbtc', validateSunset, getAddress, watchAddressAndSend); -router.get('/getinfo', setUser, postLimiter, authenticator, sendLightningInfo); +router.get('/checkpayment/:payment_hash', sendPaymentCheck); -router.get('/gettxs', setUser, postLimiter,authenticator, getAddress, refreshTxList, getUserTxsAndSend); +router.get('/balance', getAddress, refreshTxList, sendBalance); -router.get('/getuserinvoices', setUser, postLimiter, authenticator, getUserInvoicesAndSend) +router.get('/getinfo', sendLightningInfo); -router.get('/getpending', setUser, postLimiter, authenticator, getAddress, refreshTxList, sendPendingTxs); +router.get('/gettxs', getAddress, refreshTxList, getUserTxsAndSend); -router.get('/decodeinvoice', setUser, postLimiter, authenticator, validateDecodeInvocie, sendDecodedInvoice); +router.get('/getuserinvoices', getUserInvoicesAndSend) -router.get('/checkrouteinvoice', setUser, postLimiter, authenticator, validateDecodeInvocie, sendDecodedInvoice); +router.get('/getpending', getAddress, refreshTxList, sendPendingTxs); -router.get('/queryroutes/:source/:dest/:amt', setUser, postLimiter, authenticator, sendRoutes); +router.get('/decodeinvoice', validateDecodeInvocie, sendDecodedInvoice); -router.get('/getchaninfo/:chanid', setUser, postLimiter, authenticator, sendChainInfo); +router.get('/checkrouteinvoice', validateDecodeInvocie, sendDecodedInvoice); + +router.get('/queryroutes/:source/:dest/:amt', sendRoutes); + +router.get('/getchaninfo/:chanid', sendChainInfo); module.exports = router; From 353f1c10da8faedd2aca87f90f4bbc33036a3384 Mon Sep 17 00:00:00 2001 From: Denys Bitaccess Date: Wed, 29 Dec 2021 11:15:30 +0100 Subject: [PATCH 11/11] fix: individual getters Signed-off-by: Denys Bitaccess --- controllers/api.js | 54 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/controllers/api.js b/controllers/api.js index c7ec9186..dac2b84c 100644 --- a/controllers/api.js +++ b/controllers/api.js @@ -368,7 +368,22 @@ async function getUserInvoicesAndSend(req, res) { } } +async function getUserInvoiceAndSend(req, res) { + const invoiceHash = req.params.invoice_hash; + logger.log(`/getuserinvoice/${invoiceHash}`, [req.id, 'userid: ' + req.locals.user.getUserId()]); + + try { + const invoice = await req.locals.user.getUserInvoiceByHash(invoiceHash) + res.send(invoice || {}) + } catch (Err) { + logger.log('', [req.id, 'error getting user invoice ' + invoiceHash + ':', Err.message, 'userid:', req.locals.user.getUserId()]); + res.send({}); + } +} + async function getUserTxsAndSend(req, res) { + logger.log('/gettxs', [req.id, 'userid: ' + req.locals.user.getUserId()]); + try { let txs = await req.locals.user.getTxs(); let lockedPayments = await req.locals.user.getLockedPayments(); @@ -388,6 +403,41 @@ async function getUserTxsAndSend(req, res) { } } +async function getUserTxAndSend(req, res) { + const txHash = req.params.tx_hash; + logger.log(`/gettxs/${txHash}`, [req.id, 'userid: ' + req.locals.user.getUserId()]); + + try { + let tx = await req.locals.user.getLightningTxByHash(txHash) + if (tx) { + return res.send(tx) + } + + let lockedPayments = await req.locals.user.getLockedPayments(); + tx = lockedPayments.find(tx => Buffer.from(tx.r_hash).toString('hex') === hash) + if (tx) { + return res.send({ + type: 'paid_invoice', + fee: Math.floor(tx.amount * forwardFee) /* feelimit */, + value: tx.amount + Math.floor(tx.amount * forwardFee) /* feelimit */, + timestamp: tx.timestamp, + memo: 'Payment in transition', + r_hash: tx.r_hash + }) + } + + tx = await req.locals.user.getOnchainTxById(txHash); + if (tx) { + return res.send(tx) + } + + res.send({}); + } catch (Err) { + logger.log('', [req.id, 'error gettxs:', Err.message, 'userid:', req.locals.user.getUserId()]); + res.send({}); + } +} + async function payInvoiceAndSend(req, res) { logger.log('/payinvoice', [req.id, 'userid: ' + req.locals.user.getUserId(), 'invoice: ' + req.body.invoice]); @@ -539,10 +589,14 @@ router.get('/balance', getAddress, refreshTxList, sendBalance); router.get('/getinfo', sendLightningInfo); +router.get('/gettxs/:tx_hash', getAddress, refreshTxList, getUserTxAndSend); + router.get('/gettxs', getAddress, refreshTxList, getUserTxsAndSend); router.get('/getuserinvoices', getUserInvoicesAndSend) +router.get('/getuserinvoices/:invoice_hash', getUserInvoiceAndSend) + router.get('/getpending', getAddress, refreshTxList, sendPendingTxs); router.get('/decodeinvoice', validateDecodeInvocie, sendDecodedInvoice);