Skip to content

Commit

Permalink
feat: add jws validation on inbound server and update reconfig for jw…
Browse files Browse the repository at this point in the history
…s signing (#151)

* chore: add missing standard-version

* chore(snapshot): 15.0.0-snapshot.0

* chore(snapshot): 15.0.0-snapshot.1

* feat: add jws validator to inbound server

* chore: change config

* chore: test, dep, audit

* chore: changes

* chore: audit

* chore: stuff

* chore: remove faulty restart

* chore: update

* chore: test

* chore: audit

* chore: audit

* chore: comment

* chore: audit

* chore: test

* chore: test

* chore: lint

* chore: dep
  • Loading branch information
kleyow authored Jun 24, 2022
1 parent 5505711 commit bc19c32
Show file tree
Hide file tree
Showing 31 changed files with 519 additions and 112 deletions.
2 changes: 1 addition & 1 deletion audit-resolve.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,4 +188,4 @@
},
"rules": {},
"version": 1
}
}
2 changes: 2 additions & 0 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ services:
- ./docker/wait4:/opt/thirdparty-sdk/wait4
environment:
- NODE_ENV=integration
# - CONTROL_MGMT_API_WS_URL=172.17.0.1
# - CONTROL_MGMT_API_WS_PORT=4010
networks:
- mojaloop-net
ports:
Expand Down
77 changes: 58 additions & 19 deletions package-lock.json

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

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@
"@mojaloop/sdk-standard-components": "17.0.4",
"ajv": "^8.11.0",
"ajv-keywords": "5.1.0",
"atob": "^2.1.2",
"axios": "^0.27.2",
"blipp": "^4.0.2",
"commander": "7.2.0",
Expand Down Expand Up @@ -172,9 +173,9 @@
"jest-mock-process": "^1.5.1",
"lint-staged": "^13.0.2",
"multi-file-swagger": "^2.3.0",
"nodemon": "^2.0.16",
"nodemon": "^2.0.18",
"npm-audit-resolver": "^3.0.0-7",
"npm-check-updates": "^14.0.2",
"npm-check-updates": "^14.1.1",
"openapi-response-validator": "^12.0.0",
"openapi-typescript": "^5.4.0",
"prettier": "^2.7.1",
Expand Down
35 changes: 19 additions & 16 deletions src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,10 @@ import { stop } from './server/start'
export async function mkStartAPI(
api: ServerAPI,
handlers: { [handler: string]: Handler },
serviceConfig: ServiceConfig = config,
restart = false
serviceConfig: ServiceConfig = config
): Promise<HapiServer> {
// update config from program parameters,
// so setupAndStart will know on which PORT/HOST bind the server
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const apiConfig: OutConfig = serviceConfig[api] as OutConfig

// resolve the path to openapi v3 definition file
Expand All @@ -45,14 +42,14 @@ export async function mkStartAPI(
port: apiConfig.port,
host: apiConfig.host,
api,
tls: apiConfig.tls
tls: apiConfig.tls,
// we are just going pass all of the config here for convenience purposes
// so that when the server is restarted with a config that isn't on the local
// disk
serviceConfig: serviceConfig
}
// setup & start @hapi server
if (restart) {
return await index.server.setupAndRestart(serverConfig, apiPath, joinedHandlers)
} else {
return await index.server.setupAndStart(serverConfig, apiPath, joinedHandlers)
}
return await index.server.setupAndStart(serverConfig, apiPath, joinedHandlers)
}

async function GetUpdatedConfigFromMgmtAPI(
Expand All @@ -71,28 +68,32 @@ async function GetUpdatedConfigFromMgmtAPI(
export class Server {
private conf!: ServiceConfig
private controlConfig!: ControlConfig
public logger!: SDKLogger.Logger
public inboundServer!: HapiServer
public outboundServer!: HapiServer
public controlClient!: ControlAgent.Client

async initialize(conf: ServiceConfig): Promise<void> {
this.conf = conf
this.controlConfig = conf.control

this.logger = new SDKLogger.Logger()
// Check Management API for updated config
// We only start the control client if we're running within Mojaloop Payment Manager.
// The control server is the Payment Manager Management API Service.
// We only start the client to connect to and listen to the Management API service for
// management protocol messages e.g configuration changes, certificate updates etc.
if (config.pm4mlEnabled) {
const logger = new SDKLogger.Logger()
this.controlClient = await ControlAgent.Client.Create(
this.controlConfig.mgmtAPIWsUrl,
this.controlConfig.mgmtAPIWsPort,
conf
)
const updatedConfigFromMgmtAPI = await GetUpdatedConfigFromMgmtAPI(this.controlConfig, logger, this.controlClient)
logger.info(`updatedConfigFromMgmtAPI: ${JSON.stringify(updatedConfigFromMgmtAPI)}`)
const updatedConfigFromMgmtAPI = await GetUpdatedConfigFromMgmtAPI(
this.controlConfig,
this.logger,
this.controlClient
)
this.logger.info(`updatedConfigFromMgmtAPI: ${JSON.stringify(updatedConfigFromMgmtAPI)}`)
_.merge(this.conf, updatedConfigFromMgmtAPI)

this.controlClient.on(ControlAgent.EVENT.RECONFIGURE, this.restart.bind(this))
Expand All @@ -110,8 +111,10 @@ export class Server {
}

async restart(conf: ServiceConfig) {
this.inboundServer = await mkStartAPI(ServerAPI.inbound, Handlers.Inbound, conf, true)
this.outboundServer = await mkStartAPI(ServerAPI.outbound, Handlers.Outbound, conf, true)
this.logger.info(`Received new config. Restarting servers: ${JSON.stringify(conf)}`)
await Promise.all([stop(this.inboundServer), stop(this.outboundServer)])
this.inboundServer = await mkStartAPI(ServerAPI.inbound, Handlers.Inbound, conf)
this.outboundServer = await mkStartAPI(ServerAPI.outbound, Handlers.Outbound, conf)
await Promise.all([this.inboundServer, this.outboundServer])
}

Expand Down
5 changes: 2 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,9 @@
- Paweł Marzec <pawel.marzec@modusbox.com>
--------------
******/
import { setupAndStart, setupAndRestart } from './server'
import { setupAndStart } from './server'
export default {
server: {
setupAndStart,
setupAndRestart
setupAndStart
}
}
1 change: 1 addition & 0 deletions src/models/pispTransaction.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ export class PISPTransactionModel extends PersistentModel<PISPTransactionStateMa
currentState: this.data.currentState as OutboundAPI.Schemas.ThirdpartyTransactionPartyLookupState
}
} catch (error) {
console.log(error)
this.logger.push({ error }).error('onRequestPartyLookup -> requestPartiesInformation')
// try to handle specific HTTP related error
if (error instanceof HTTPResponseError) {
Expand Down
6 changes: 5 additions & 1 deletion src/server/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { Server } from '@hapi/hapi'
import onValidateFail from '~/handlers/shared/onValidateFail'
import { validateRoutes } from '@mojaloop/central-services-error-handling'
import { BaseRequestTLSConfig } from '@mojaloop/sdk-standard-components'
import { ServiceConfig } from '~/shared/config'

// distinguish APIs exposed
export enum ServerAPI {
Expand All @@ -41,11 +42,13 @@ export interface ServerConfig {
// the exposed api descriptor
api: ServerAPI
tls: BaseRequestTLSConfig
serviceConfig: ServiceConfig
}
// server app interface accessible in handlers and plugins via settings.app[key]
export interface ServerApp {
// specify which API is exposed
api: ServerAPI
serviceConfig: ServiceConfig
}

export default async function create(config: ServerConfig): Promise<Server> {
Expand All @@ -59,7 +62,8 @@ export default async function create(config: ServerConfig): Promise<Server> {
}
},
app: {
api: config.api
api: config.api,
serviceConfig: config.serviceConfig
},
// only the inbound hapi server needs tls enabled
tls: config.api == ServerAPI.inbound && config.tls.mutualTLS.enabled ? config.tls.creds : false
Expand Down
4 changes: 2 additions & 2 deletions src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@
--------------
******/
import Handlers from '~/handlers'
import { setupAndStart, setupAndRestart } from './setupAndStart'
import { setupAndStart } from './setupAndStart'

export { Server } from '@hapi/hapi'
export { ServerAPI, ServerApp, ServerConfig } from './create'
export { Handlers, setupAndStart, setupAndRestart }
export { Handlers, setupAndStart }
15 changes: 14 additions & 1 deletion src/server/plugins/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,19 @@ import OpenAPI from './openAPI'
import { StatePlugin } from './state'
import { Util } from '@mojaloop/central-services-shared'
import Vision from '@hapi/vision'
import { jwsValidatorPlugin } from './jwsValidator'
import { ServerAPI, ServerApp } from '../create'

async function register(server: Server, apiPath: string, handlers: { [handler: string]: Handler }): Promise<Server> {
const openapiBackend = await OpenAPI.initialize(apiPath, handlers)
const plugins = [

const api = (server.settings.app as ServerApp).api

let plugins = [
StatePlugin,
// only the inbound server needs the jws validation
// order of plugins is important, giving it a high priority seems fitting
api == ServerAPI.inbound ? jwsValidatorPlugin : null,
Util.Hapi.OpenapiBackendValidator,
Good,
openapiBackend,
Expand All @@ -52,6 +60,11 @@ async function register(server: Server, apiPath: string, handlers: { [handler: s
// Util.Hapi.FSPIOPHeaderValidation
]

// filter out any null values
plugins = plugins.filter(function (e) {
return e != null
})

await server.register(plugins)

// use as a catch-all handler
Expand Down
Loading

0 comments on commit bc19c32

Please sign in to comment.