Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ package-lock.json
keys/index.js
node_modules/
*.log
private-tests/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 2 additions & 2 deletions keys/index-example.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
126 changes: 125 additions & 1 deletion lib/pushtx-wrappers/samourai-backend/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
}

/**
Expand All @@ -32,7 +41,7 @@ class Wrapper extends AbstractPushTxWrapper {

try {
const params = {
url: `${this.options.url}`,
url: `${this.options.url}/pushtx/`,
method: 'POST',
form: {
tx: `${rawtx}`
Expand All @@ -42,8 +51,15 @@ 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 "<txid>"
Logger.info(`Successfully pushed ${result.data} over ${this.name}`)
Expand Down Expand Up @@ -71,6 +87,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