Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(csi-411): disable validation on proxied request #346

Merged
merged 11 commits into from
Aug 12, 2024
Merged
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
2 changes: 1 addition & 1 deletion docker/quoting-service/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"includeCauseExtension": false,
"truncateExtensions": true
},
"SIMPLE_ROUTING_MODE": true,
"SIMPLE_ROUTING_MODE": false,
"ENDPOINT_SECURITY":{
"JWS": {
"JWS_SIGN": true,
Expand Down
38 changes: 24 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@
"@mojaloop/sdk-standard-components": "18.1.0",
"ajv": "8.17.1",
"ajv-keywords": "5.1.0",
"axios": "1.7.2",
"axios": "1.7.3",
"blipp": "4.0.2",
"commander": "12.1.0",
"event-stream": "4.0.1",
Expand All @@ -138,10 +138,10 @@
"audit-ci": "^7.1.0",
"eslint": "8.16.0",
"eslint-config-standard": "17.1.0",
"eslint-plugin-jest": "28.6.0",
"eslint-plugin-jest": "28.7.0",
"jest": "29.7.0",
"jest-junit": "16.0.0",
"npm-check-updates": "17.0.0",
"npm-check-updates": "17.0.3",
"nyc": "17.0.0",
"pre-commit": "1.2.2",
"proxyquire": "2.1.3",
Expand Down
48 changes: 43 additions & 5 deletions src/lib/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,29 +206,67 @@ function calculateRequestHash (request) {
}

// Add caching to the participant endpoint
const fetchParticipantInfo = async (source, destination, cache) => {
const fetchParticipantInfo = async (source, destination, cache, proxyClient) => {
// Get quote participants from central ledger admin
const { switchEndpoint } = config
const url = `${switchEndpoint}/participants`
let requestPayer
let requestPayee
const cachedPayer = cache && cache.get(`fetchParticipantInfo_${source}`)
const cachedPayee = cache && cache.get(`fetchParticipantInfo_${destination}`)

if (!cachedPayer) {
if (proxyClient) {
if (!proxyClient.isConnected) await proxyClient.connect()
const proxyIdSource = await proxyClient.lookupProxyByDfspId(source)
const proxyIdDestination = await proxyClient.lookupProxyByDfspId(destination)
if (proxyIdSource) {
// construct participant adjacent data structure that uses the original
// participant when they are proxied and out of scheme
requestPayer = {
data: {
name: source,
id: '',
// assume source is active
isActive: 1,
links: { self: '' },
accounts: [],
proxiedParticipant: true
}
}
}
if (proxyIdDestination) {
// construct participant adjacent data structure that uses the original
// participant when they are proxied and out of scheme
requestPayee = {
data: {
name: destination,
id: '',
// assume destination is active
isActive: 1,
links: { self: '' },
accounts: [],
proxiedParticipant: true
}
}
}
}

const cachedPayer = cache && !requestPayer && cache.get(`fetchParticipantInfo_${source}`)
const cachedPayee = cache && !requestPayee && cache.get(`fetchParticipantInfo_${destination}`)

if (!cachedPayer && !requestPayer) {
requestPayer = await axios.request({ url: `${url}/${source}` })
cache && cache.put(`fetchParticipantInfo_${source}`, requestPayer, Config.participantDataCacheExpiresInMs)
Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache miss for payer ${source}`)
} else {
Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache hit for payer ${source}`)
}
if (!cachedPayee) {
if (!cachedPayee && !requestPayee) {
requestPayee = await axios.request({ url: `${url}/${destination}` })
cache && cache.put(`fetchParticipantInfo_${destination}`, requestPayee, Config.participantDataCacheExpiresInMs)
Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache miss for payer ${source}`)
} else {
Logger.isDebugEnabled && Logger.debug(`${new Date().toISOString()}, [fetchParticipantInfo]: cache hit for payee ${destination}`)
}

const payer = cachedPayer || requestPayer.data
const payee = cachedPayee || requestPayee.data
return { payer, payee }
Expand Down
35 changes: 26 additions & 9 deletions src/model/quotes.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,11 @@ class QuotesModel {

// In fspiop api spec 2.0, to support FX, `supportedCurrencies` can be optionally passed in via the payer property.
// If `supportedCurrencies` is present, then payer FSP must have position accounts for all those currencies.
if (quoteRequest.payer.supportedCurrencies && quoteRequest.payer.supportedCurrencies.length > 0) {
if (quoteRequest.payer.supportedCurrencies &&
quoteRequest.payer.supportedCurrencies.length > 0 &&
// if the payer dfsp has a proxy cache entry, we do not validate the dfsp here
!(await this.proxyClient?.lookupProxyByDfspId(fspiopSource))
) {
await Promise.all(quoteRequest.payer.supportedCurrencies.map(currency =>
this.db.getParticipant(fspiopSource, LOCAL_ENUM.PAYER_DFSP, currency, ENUM.Accounts.LedgerAccountType.POSITION)
))
Expand All @@ -192,7 +196,9 @@ class QuotesModel {
// Lets make sure the optional fspId exists in the payer's partyIdInfo before we validate it
if (
quoteRequest.payer?.partyIdInfo?.fspId &&
quoteRequest.payer.partyIdInfo.fspId !== fspiopSource
quoteRequest.payer.partyIdInfo.fspId !== fspiopSource &&
// if the payer dfsp has a proxy cache entry, we do not validate the dfsp here
!(await this.proxyClient?.lookupProxyByDfspId(quoteRequest.payer.partyIdInfo.fspId))
) {
await this.db.getParticipant(quoteRequest.payer.partyIdInfo.fspId, LOCAL_ENUM.PAYER_DFSP, quoteRequest.amount.currency, ENUM.Accounts.LedgerAccountType.POSITION)
}
Expand All @@ -214,9 +220,17 @@ class QuotesModel {
* @returns {promise} - promise will reject if request is not valid
*/
async validateQuoteUpdate (headers, quoteUpdateRequest) {
if (this.proxyClient?.isConnected === false) await this.proxyClient.connect()
const fspiopSource = headers[ENUM.Http.Headers.FSPIOP.SOURCE]
const payeeCurrency = quoteUpdateRequest.payeeReceiveAmount?.currency || quoteUpdateRequest.transferAmount.currency
await this.db.getParticipant(fspiopSource, LOCAL_ENUM.PAYEE_DFSP, payeeCurrency, ENUM.Accounts.LedgerAccountType.POSITION)
let proxyIdSource
if (this.proxyClient) {
proxyIdSource = await this.proxyClient.lookupProxyByDfspId(fspiopSource)
}
// skip fulfil validation if the source is a proxy
if (!proxyIdSource) {
const payeeCurrency = quoteUpdateRequest.payeeReceiveAmount?.currency || quoteUpdateRequest.transferAmount.currency
await this.db.getParticipant(fspiopSource, LOCAL_ENUM.PAYEE_DFSP, payeeCurrency, ENUM.Accounts.LedgerAccountType.POSITION)
}
}

/**
Expand Down Expand Up @@ -245,7 +259,7 @@ class QuotesModel {
// validate - this will throw if the request is invalid
await this.validateQuoteRequest(fspiopSource, fspiopDestination, quoteRequest)

const { payer, payee } = await fetchParticipantInfo(fspiopSource, fspiopDestination, cache)
const { payer, payee } = await fetchParticipantInfo(fspiopSource, fspiopDestination, cache, this.proxyClient)
this.writeLog(`Got payer ${payer} and payee ${payee}`)

// Run the rules engine. If the user does not want to run the rules engine, they need only to
Expand Down Expand Up @@ -304,12 +318,12 @@ class QuotesModel {
this.db.getAmountType(quoteRequest.amountType),
this.db.getPartyType(LOCAL_ENUM.PAYER),
this.db.getPartyIdentifierType(quoteRequest.payer.partyIdInfo.partyIdType),
this.db.getParticipantByName(quoteRequest.payer.partyIdInfo.fspId),
payer.proxiedParticipant ? null : this.db.getParticipantByName(quoteRequest.payer.partyIdInfo.fspId),
this.db.getTransferParticipantRoleType(LOCAL_ENUM.PAYER_DFSP),
this.db.getLedgerEntryType(LOCAL_ENUM.PRINCIPLE_VALUE),
this.db.getPartyType(LOCAL_ENUM.PAYEE),
this.db.getPartyIdentifierType(quoteRequest.payee.partyIdInfo.partyIdType),
this.db.getParticipantByName(quoteRequest.payee.partyIdInfo.fspId),
payee.proxiedParticipant ? null : this.db.getParticipantByName(quoteRequest.payee.partyIdInfo.fspId),
this.db.getTransferParticipantRoleType(LOCAL_ENUM.PAYEE_DFSP),
this.db.getLedgerEntryType(LOCAL_ENUM.PRINCIPLE_VALUE)
])
Expand All @@ -328,8 +342,11 @@ class QuotesModel {

// create a txn reference
this.writeLog(`Creating transactionReference for quoteId: ${quoteRequest.quoteId} and transactionId: ${quoteRequest.transactionId}`)
refs.transactionReferenceId = await this.db.createTransactionReference(txn,
quoteRequest.quoteId, quoteRequest.transactionId)
refs.transactionReferenceId = await this.db.createTransactionReference(
txn,
quoteRequest.quoteId,
quoteRequest.transactionId
)
this.writeLog(`transactionReference created transactionReferenceId: ${refs.transactionReferenceId}`)

// create the quote row itself
Expand Down
Loading