From cb5fcf7029884c93a865258ce08e443a92f7e625 Mon Sep 17 00:00:00 2001 From: Abhishek Y Date: Sat, 22 Jun 2024 15:35:43 +0530 Subject: [PATCH] Added optional shared key authentication for BPP Webhook communication --- src/controllers/bpp.request.controller.ts | 9 ++++--- src/schemas/configs/app.config.schema.ts | 4 ++- src/utils/auth.utils.ts | 31 +++++++++++++++++++---- src/utils/callback.utils.ts | 14 +++++----- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/src/controllers/bpp.request.controller.ts b/src/controllers/bpp.request.controller.ts index cb094ab..1a3626f 100644 --- a/src/controllers/bpp.request.controller.ts +++ b/src/controllers/bpp.request.controller.ts @@ -5,10 +5,8 @@ import * as AmqbLib from "amqplib"; import { Exception, ExceptionType } from "../models/exception.model"; import { acknowledgeACK } from "../utils/acknowledgement.utils"; import { GatewayUtils } from "../utils/gateway.utils"; -import { ActionUtils } from "../utils/actions.utils"; import { RequestCache } from "../utils/cache/request.cache.utils"; import { parseRequestCache } from "../schemas/cache/request.cache.schema"; -import { getSubscriberDetails } from "../utils/lookup.utils"; import { Locals } from "../interfaces/locals.interface"; import moment from "moment"; import { getConfig } from "../utils/config.utils"; @@ -19,6 +17,7 @@ import { createTelemetryEvent, processTelemetry } from "../utils/telemetry.utils"; +import { createBppWebhookAuthHeaderConfig } from "../utils/auth.utils"; export const bppNetworkRequestHandler = async ( req: Request, @@ -92,7 +91,11 @@ export const bppNetworkRequestSettler = async ( break; } case ClientConfigType.webhook: { - requestCallback(requestBody); + let axios_config = {}; + if (getConfig().app.useHMACForWebhook) { + axios_config = await createBppWebhookAuthHeaderConfig(requestBody); + } + requestCallback(requestBody, axios_config); break; } case ClientConfigType.messageQueue: { diff --git a/src/schemas/configs/app.config.schema.ts b/src/schemas/configs/app.config.schema.ts index 4160bae..180018e 100644 --- a/src/schemas/configs/app.config.schema.ts +++ b/src/schemas/configs/app.config.schema.ts @@ -54,7 +54,9 @@ export const appConfigSchema = z.object({ mandateLayer2Config: z.boolean().optional(), unsolicitedWebhook: z.object({ url: z.string().optional() - }).optional() + }).optional(), + useHMACForWebhook: z.boolean().optional(), + sharedKeyForWebhookHMAC: z.string().optional(), }); export type AppConfigDataType = z.infer; diff --git a/src/utils/auth.utils.ts b/src/utils/auth.utils.ts index 7ed0d38..28c4040 100644 --- a/src/utils/auth.utils.ts +++ b/src/utils/auth.utils.ts @@ -70,9 +70,8 @@ export const createAuthorizationHeader = async (message: any) => { getConfig().app.privateKey || "" ); const subscriber_id = getConfig().app.subscriberId; - const header = `Signature keyId="${subscriber_id}|${ - getConfig().app.uniqueKey - }|ed25519",algorithm="ed25519",created="${created}",expires="${expires}",headers="(created) (expires) digest",signature="${signature}"`; + const header = `Signature keyId="${subscriber_id}|${getConfig().app.uniqueKey + }|ed25519",algorithm="ed25519",created="${created}",expires="${expires}",headers="(created) (expires) digest",signature="${signature}"`; return header; }; @@ -204,8 +203,30 @@ export const createAuthHeaderConfig = async (request: any) => { timeout: getConfig().app.httpTimeout }; logger.info( - `Axios Config for Request from ${ - getConfig().app.mode + " " + getConfig().app.gateway.mode + `Axios Config for Request from ${getConfig().app.mode + " " + getConfig().app.gateway.mode + }:==>\n${JSON.stringify(axios_config)}\n\n` + ); + return axios_config; +}; + +const createBppWebhookAuthHeader = async (request: any) => { + const sodium = _sodium; + const key = getConfig().app.sharedKeyForWebhookHMAC || ""; + const keyUint8Array = sodium.from_string(key); + const messageUint8Array = sodium.from_string(JSON.stringify(request)); + const hmac = sodium.crypto_auth(messageUint8Array, keyUint8Array); + return sodium.to_hex(hmac); +}; + +export const createBppWebhookAuthHeaderConfig = async (request: any) => { + const header = await createBppWebhookAuthHeader(request); + const axios_config = { + headers: { + authorization: header + } + }; + logger.info( + `Axios Config for Request from ${getConfig().app.mode + " " + getConfig().app.gateway.mode }:==>\n${JSON.stringify(axios_config)}\n\n` ); return axios_config; diff --git a/src/utils/callback.utils.ts b/src/utils/callback.utils.ts index ae60010..3e4f664 100644 --- a/src/utils/callback.utils.ts +++ b/src/utils/callback.utils.ts @@ -1,8 +1,7 @@ -import axios from "axios"; +import axios, { AxiosRequestConfig } from "axios"; import { Exception, ExceptionType } from "../models/exception.model"; import { - BecknErrorDataType, - becknErrorSchema + BecknErrorDataType } from "../schemas/becknError.schema"; import { requestCallbackSchema } from "../schemas/callbacks/request.callback.schema"; import { responseCallbackSchema } from "../schemas/callbacks/response.callback.schema"; @@ -10,11 +9,10 @@ import { ClientConfigType, WebhookClientConfigDataType } from "../schemas/configs/client.config.schema"; -import { GatewayMode } from "../schemas/configs/gateway.app.config.schema"; import { getConfig } from "./config.utils"; import logger from "./logger.utils"; -async function makeClientCallback(data: any) { +async function makeClientCallback(data: any, axios_config?: AxiosRequestConfig) { try { if (getConfig().client.type != ClientConfigType.webhook) { throw new Exception( @@ -30,7 +28,7 @@ async function makeClientCallback(data: any) { console.log( `TMTR - ${data?.context?.message_id} - ${getConfig().app.mode}-${getConfig().app.gateway.mode} FORW EXIT: ${new Date().valueOf()}` ); - const response = await axios.post(clientConnectionConfig.url, data); + const response = await axios.post(clientConnectionConfig.url, data, axios_config || {}); logger.info( `Response from Webhook:==>\n ${JSON.stringify( response.data @@ -88,10 +86,10 @@ export async function responseCallback(data: any) { } } -export async function requestCallback(data: any) { +export async function requestCallback(data: any, axios_config?: AxiosRequestConfig) { try { const callbackData = requestCallbackSchema.parse(data); - await makeClientCallback(callbackData); + await makeClientCallback(callbackData, axios_config); } catch (error) { if (error instanceof Exception) { throw error;