Skip to content

Commit

Permalink
feat: add request forwarding for final stages of account linking (#57)
Browse files Browse the repository at this point in the history
* feat: add request forwarding for final stages of account linking

* feat: add PATCH /consents/{ID} handling

* chore: remove code

* chore: lint fix
  • Loading branch information
kleyow authored Jun 14, 2021
1 parent 581ae36 commit f167523
Show file tree
Hide file tree
Showing 8 changed files with 312 additions and 32 deletions.
28 changes: 10 additions & 18 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
"@mojaloop/central-services-health": "^13.0.0",
"@mojaloop/central-services-logger": "10.6.1",
"@mojaloop/central-services-metrics": "11.0.0",
"@mojaloop/central-services-shared": "^13.0.3",
"@mojaloop/central-services-shared": "^13.0.4",
"@mojaloop/central-services-stream": "^10.7.0",
"@mojaloop/event-sdk": "^10.7.1",
"@types/hapi": "^18.0.5",
Expand Down
5 changes: 3 additions & 2 deletions src/domain/consents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,9 @@ export async function forwardConsentsIdRequest (
headers: HapiUtil.Dictionary<string>,
method: RestMethodsEnum,
payload: tpAPI.Schemas.ConsentsIDPutResponseVerified |
tpAPI.Schemas.ConsentsIDPutResponseSigned,
tpAPI.Schemas.ConsentsIDPutResponseSigned |
tpAPI.Schemas.ConsentsIDPatchResponseVerified |
tpAPI.Schemas.ConsentsIDPatchResponseRevoked,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
span?: any): Promise<void> {
const childSpan = span?.getChild('forwardConsentsIdRequest')
Expand Down Expand Up @@ -220,7 +222,6 @@ export async function forwardConsentsRequest (
Enum.Http.ResponseTypes.JSON,
childSpan
)
// todo: forward request to auth-service as well

Logger.info(`consents::forwardConsentsRequest - Forwarded consents: from ${sourceDfspId} to ${destinationDfspId}`)
if (childSpan && !childSpan.isFinished) {
Expand Down
56 changes: 54 additions & 2 deletions src/server/handlers/consents/{ID}.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import { thirdparty as tpAPI } from '@mojaloop/api-snippets'
import { getSpanTags } from '~/shared/util'
import { forwardConsentsIdRequest } from '~/domain/consents'


/**
* summary: UpdateConsent
* description: The method PUT /consents/ID is called by both a PISP and DFSP
Expand Down Expand Up @@ -86,7 +85,60 @@ async function put(_context: unknown, request: Request, h: ResponseToolkit): Pro
}
}

/**
* summary: PatchConsentByID
* description: The method PATCH /consents/ID is called by DFSP or Auth Service
* parameters: body, content-length
* produces: application/json
* responses: 200, 400, 401, 403, 404, 405, 406, 501, 503
*/
async function patch(_context: unknown, request: Request, h: ResponseToolkit): Promise<ResponseObject> {
const span = (request as any).span
// Trust that hapi parsed the ID and Payload for us
const consentsRequestId: string = request.params.ID
const payload = request.payload as
tpAPI.Schemas.ConsentsIDPatchResponseVerified |
tpAPI.Schemas.ConsentsIDPatchResponseRevoked

try {
const tags: { [id: string]: string } = getSpanTags(
request,
Enum.Events.Event.Type.CONSENT,
Enum.Events.Event.Action.PATCH,
{ consentsRequestId })

span?.setTags(tags)
await span?.audit({
headers: request.headers,
payload: request.payload
}, AuditEventAction.start)

// Note: calling async function without `await`
forwardConsentsIdRequest(
consentsRequestId,
Enum.EndPoints.FspEndpointTemplates.TP_CONSENT_PATCH,
Enum.EndPoints.FspEndpointTypes.TP_CB_URL_CONSENT_PATCH,
request.headers,
Enum.Http.RestMethods.PATCH,
payload,
span
)
.catch(err => {
// Do nothing with the error - forwardConsentsIdRequest takes care of async errors
Logger.error('Consents::patch - forwardConsentsIdRequest async handler threw an unhandled error')
Logger.error(ReformatFSPIOPError(err))
})

return h.response().code(Enum.Http.ReturnCodes.OK.CODE)
} catch (err) {
const fspiopError = ReformatFSPIOPError(err)
Logger.error(fspiopError)
throw fspiopError
}
}

export default {
put
put,
patch
}

8 changes: 8 additions & 0 deletions src/server/handlers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,14 @@ export default {
['success']
]
),
PatchConsentByID: wrapWithHistogram(
ConsentsId.patch,
[
'consentsId_patch',
'Patch consentsId request',
['success']
]
),
NotifyErrorConsents: wrapWithHistogram(
ConsentsByIdError.put,
[
Expand Down
29 changes: 29 additions & 0 deletions test/unit/data/mockData.json
Original file line number Diff line number Diff line change
Expand Up @@ -620,5 +620,34 @@
}
}
}
},
"patchConsentsByIdRequestVerified": {
"headers": {
"fspiop-source": "dfspA",
"fspiop-destination": "pispA"
},
"params": {
"ID": "a5bbfd51-d9fc-4084-961a-c2c2221a31e0"
},
"payload": {
"credential": {
"status": "VERIFIED"
}
}
},
"patchConsentsByIdRequestRevoked": {
"headers": {
"fspiop-source": "dfspA",
"fspiop-destination": "pispA"
},
"params": {
"ID": "a5bbfd51-d9fc-4084-961a-c2c2221a31e0"
},
"payload": {
"credential": {
"status": "VERIFIED",
"revokedAt": "some-date-time"
}
}
}
}
40 changes: 39 additions & 1 deletion test/unit/domain/consents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const mockData = JSON.parse(JSON.stringify(TestData))
const mockConsentsPostRequestPISP = mockData.consentsPostRequestPISP
const mockConsentsIdPutRequest = mockData.consentsIdPutRequestVerified
const mockConsentsPostGenerateChallengeRequest = mockData.consentsGenerateChallengeRequest
const mockConsentIdPatchRequestVerified = mockData.patchConsentsByIdRequestVerified

const getEndpointForwardConsentsRequestExpected = [
'http://central-ledger.local:3001',
Expand Down Expand Up @@ -134,6 +135,26 @@ const sendRequestforwardConsentsIdRequestExpected = [
expect.objectContaining({ isFinished: false })
]


const getEndpointforwardConsentsIdRequestExpectedPatchRequest = [
'http://central-ledger.local:3001',
mockConsentIdPatchRequestVerified.headers['fspiop-destination'],
Enum.EndPoints.FspEndpointTypes.TP_CB_URL_CONSENT_PATCH,
"/consents/{{ID}}",
{"ID": "09595320-51e5-4c4e-a910-c56917e4cdc4"}
]

const sendRequestforwardConsentsIdRequestExpectedPatchRequest = [
'http://dfspa-sdk/consents/09595320-51e5-4c4e-a910-c56917e4cdc4/',
mockConsentIdPatchRequestVerified.headers,
mockConsentIdPatchRequestVerified.headers['fspiop-source'],
mockConsentIdPatchRequestVerified.headers['fspiop-destination'],
Enum.Http.RestMethods.PATCH,
mockConsentIdPatchRequestVerified.payload,
Enum.Http.ResponseTypes.JSON,
expect.objectContaining({ isFinished: false })
]

describe('domain/consents', () => {
describe('forwardConsentsRequest', () => {
beforeEach((): void => {
Expand Down Expand Up @@ -295,7 +316,7 @@ describe('domain/consents/{ID}', () => {
mockLoggerError.mockReturnValue(null)
})

it('forwards POST /consents request', async (): Promise<void> => {
it('forwards PUT /consents/{ID} request', async (): Promise<void> => {
const mockSpan = new Span()
mockGetEndpointAndRender.mockResolvedValue('http://dfspa-sdk/consents/09595320-51e5-4c4e-a910-c56917e4cdc4/')
mockSendRequest.mockResolvedValue({ ok: true, status: 202, statusText: 'Accepted', payload: null })
Expand All @@ -313,6 +334,23 @@ describe('domain/consents/{ID}', () => {
expect(mockSendRequest).toHaveBeenCalledWith(...sendRequestforwardConsentsIdRequestExpected )
})

it('forwards PATCH /consents/{ID} request', async (): Promise<void> => {
const mockSpan = new Span()
mockGetEndpointAndRender.mockResolvedValue('http://dfspa-sdk/consents/09595320-51e5-4c4e-a910-c56917e4cdc4/')
mockSendRequest.mockResolvedValue({ ok: true, status: 200, statusText: 'Accepted', payload: null })
await Consents.forwardConsentsIdRequest(
'09595320-51e5-4c4e-a910-c56917e4cdc4',
Enum.EndPoints.FspEndpointTemplates.TP_CONSENT_PATCH,
Enum.EndPoints.FspEndpointTypes.TP_CB_URL_CONSENT_PATCH,
mockConsentIdPatchRequestVerified.headers,
Enum.Http.RestMethods.PATCH,
mockConsentIdPatchRequestVerified.payload,
mockSpan
)

expect(mockGetEndpointAndRender).toHaveBeenCalledWith(...getEndpointforwardConsentsIdRequestExpectedPatchRequest)
expect(mockSendRequest).toHaveBeenCalledWith(...sendRequestforwardConsentsIdRequestExpectedPatchRequest)
})

it('handles `getEndpoint` failure', async (): Promise<void> => {
const mockSpan = new Span()
Expand Down
Loading

0 comments on commit f167523

Please sign in to comment.