From d9687ea8a2f0d90041fce3ac0654259d23ed8d34 Mon Sep 17 00:00:00 2001 From: LaurentMT Date: Fri, 17 May 2019 15:09:06 +0200 Subject: [PATCH 1/4] Modified semantic of "options.url" config parameter used by samourai-backend PushTxWrapper. --- keys/index-example.js | 4 ++-- lib/pushtx-wrappers/samourai-backend/wrapper.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/keys/index-example.js b/keys/index-example.js index 693b409..03c4af6 100644 --- a/keys/index-example.js +++ b/keys/index-example.js @@ -103,8 +103,8 @@ module.exports = { * name:'samourai backend mainnet', * // Configuration of the wrapper * options: { - * // URL of the pushtx endpoint - * url: "https://api.samouraiwallet.com/v2/pushtx", + * // Base url of the Samourai backend + * url: "https://api.samouraiwallet.com/v2", * // API key requested by the backend * // or null if the backend doesn't require authentication * apiKey: null diff --git a/lib/pushtx-wrappers/samourai-backend/wrapper.js b/lib/pushtx-wrappers/samourai-backend/wrapper.js index 565357d..7366246 100644 --- a/lib/pushtx-wrappers/samourai-backend/wrapper.js +++ b/lib/pushtx-wrappers/samourai-backend/wrapper.js @@ -32,7 +32,7 @@ class Wrapper extends AbstractPushTxWrapper { try { const params = { - url: `${this.options.url}`, + url: `${this.options.url}/pushtx`, method: 'POST', form: { tx: `${rawtx}` From 606877681f4be7fbe00b2a051bd3dedf112f9795 Mon Sep 17 00:00:00 2001 From: LaurentMT Date: Fri, 17 May 2019 18:07:46 +0200 Subject: [PATCH 2/4] gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 049b3db..1c6b2ac 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ package-lock.json keys/index.js node_modules/ *.log +private-tests/ From 398f7eaa9c35678ebe1d690f97c070afa5d31468 Mon Sep 17 00:00:00 2001 From: LaurentMT Date: Fri, 17 May 2019 18:08:12 +0200 Subject: [PATCH 3/4] Added support of authentication to Samourai backend (Dojo) --- README.md | 2 +- .../samourai-backend/wrapper.js | 128 +++++++++++++++++- 2 files changed, 128 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 933f65a..ad90c1c 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ THe gateway temporarily stores the segments. When all segments related to a tran The gateway is provided with a default storage system using a transient memory cache. Additional types of storage may be implemented in the future (temporary storage in a database, etc). Note that a single storage can be used at once. -For pushing transactions on the Bitcoin network, the gateway currently supports pushes through the RPC API of a bitcoin node or through the pushTx service provided by an instance of the Samourai backend. Additional pushTx services may be implemented in the future. Multiple pushTx services can be activated. The Gateway randomly selects a service for each push. +For pushing transactions on the Bitcoin network, the gateway currently supports pushes through the RPC API of a bitcoin node or through the pushTx service provided by an instance of the Samourai backend (Dojo). Additional pushTx services may be implemented in the future. Multiple pushTx services can be activated. The Gateway randomly selects a service for each push. ## Known limitations diff --git a/lib/pushtx-wrappers/samourai-backend/wrapper.js b/lib/pushtx-wrappers/samourai-backend/wrapper.js index 7366246..a4f0d00 100644 --- a/lib/pushtx-wrappers/samourai-backend/wrapper.js +++ b/lib/pushtx-wrappers/samourai-backend/wrapper.js @@ -20,6 +20,15 @@ class Wrapper extends AbstractPushTxWrapper { */ constructor(options, name) { super(options, name) + + this._jwtAccessToken = null + this._jwtRefreshToken = null + this.refreshTimeout = null + + // Authentication to backend + if (this.options.apiKey != null) { + this.getAuthTokens() + } } /** @@ -32,7 +41,7 @@ class Wrapper extends AbstractPushTxWrapper { try { const params = { - url: `${this.options.url}/pushtx`, + url: `${this.options.url}/pushtx/`, method: 'POST', form: { tx: `${rawtx}` @@ -42,19 +51,28 @@ class Wrapper extends AbstractPushTxWrapper { }, timeout: 30000 } + + // Add access token to params + if (this._jwtAccessToken != null) { + params['form']['at'] = this._jwtAccessToken + } + const res = await rp(params) const result = JSON.parse(res) + if (result.status == 'ok') { // Returned data is "" Logger.info(`Successfully pushed ${result.data} over ${this.name}`) return true } else { Logger.error(null, `A problem was met while trying to push a transaction over ${this.name}`) + Logger.error(null, `${result.error.message} (error code: ${result.error.code})`) return false } } catch(err) { try { + Logger.error(err) const error = JSON.parse(err.error) Logger.error( error.error.message, @@ -71,6 +89,114 @@ class Wrapper extends AbstractPushTxWrapper { } } + /** + * Authentication to the backend thanks to an API key + * @returns {boolean} returns true if authentication was successful, false otherwise + */ + async getAuthTokens() { + Logger.info(`Trying to authenticate to the backend`) + + try { + this.clearRefreshTimeout() + + const params = { + url: `${this.options.url}/auth/login?apikey=${this.options.apiKey}`, + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + }, + timeout: 30000 + } + const res = await rp(params) + const result = JSON.parse(res) + + this._jwtAccessToken = result['authorizations']['access_token'] + this._jwtRefreshToken = result['authorizations']['refresh_token'] + + // Schedule next refresh of the access token + await this.scheduleNextRefresh() + + Logger.info(`Successfully authenticated to the backend`) + return true + + } catch(err) { + try { + const error = JSON.parse(err.error) + Logger.error( + error.error.message, + `A problem (error code ${error.error.code}) was met while trying to authenticate to the backend` + ) + } catch(e) { + Logger.error( + null, + `A problem was met while trying to authenticate to the backend` + ) + } finally { + return false + } + } + } + + /** + * Refresh the JWT access token + * @returns {boolean} returns true if refresh was successful, false otherwise + */ + async refreshAuthToken() { + Logger.info(`Trying to refresh the access token`) + + try { + if (this._jwtRefreshToken == null) + return this.getAuthTokens() + + const params = { + url: `${this.options.url}/auth/refresh?rt=${this._jwtRefreshToken}`, + method: 'POST', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + }, + timeout: 30000 + } + const res = await rp(params) + const result = JSON.parse(res) + + this._jwtAccessToken = result['authorizations']['access_token'] + + // Schedule next refresh of the access token + this.clearRefreshTimeout() + await this.scheduleNextRefresh() + + Logger.info(`Successfully refreshed the access token`) + return true + + } catch(err) { + Logger.error( + null, + `A problem was met while trying to refresh the access token` + ) + // Try a new authentication + return this.getAuthTokens() + } + } + + /** + * Clear refreshTimeout + */ + clearRefreshTimeout() { + if (this.refreshTimeout) { + clearTimeout(this.refreshTimeout) + this.refreshTimeout = null + } + } + + /** + * Schedule next refresh of the access token + */ + async scheduleNextRefresh() { + this.refreshTimeout = setTimeout(async function() { + await this.refreshAuthToken() + }.bind(this), 600000) + } + } module.exports = Wrapper From ad3553cc706e9219a89dac7d5d99dca438df6b61 Mon Sep 17 00:00:00 2001 From: LaurentMT Date: Fri, 17 May 2019 18:13:03 +0200 Subject: [PATCH 4/4] Removed logs --- lib/pushtx-wrappers/samourai-backend/wrapper.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/pushtx-wrappers/samourai-backend/wrapper.js b/lib/pushtx-wrappers/samourai-backend/wrapper.js index a4f0d00..493daf1 100644 --- a/lib/pushtx-wrappers/samourai-backend/wrapper.js +++ b/lib/pushtx-wrappers/samourai-backend/wrapper.js @@ -66,13 +66,11 @@ class Wrapper extends AbstractPushTxWrapper { return true } else { Logger.error(null, `A problem was met while trying to push a transaction over ${this.name}`) - Logger.error(null, `${result.error.message} (error code: ${result.error.code})`) return false } } catch(err) { try { - Logger.error(err) const error = JSON.parse(err.error) Logger.error( error.error.message,