From 60727aee1483408613bb6520b4b3e1a78ad5817f Mon Sep 17 00:00:00 2001 From: tareq89 Date: Thu, 22 Aug 2024 12:54:58 +0600 Subject: [PATCH] redact sensitive user information from log --- CHANGELOG.md | 4 ++++ src/api/notification/email-service.ts | 12 ++++++++---- src/api/notification/handler.ts | 16 +++++++++++++--- src/api/notification/sms-service.ts | 10 +++++++--- src/logger.ts | 26 +++++++++++++++++++++++++- tsconfig.json | 24 +++++------------------- 6 files changed, 62 insertions(+), 30 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18e8fc73c..0a4be967e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,10 @@ - **Title** Description +### Improvements + +- Auth token, ip address, remote address, mobile number, email redacted/masked from server log + ### Infrastructure breaking changes - **Title** Description diff --git a/src/api/notification/email-service.ts b/src/api/notification/email-service.ts index 7dc063aa4..0dd2b508c 100644 --- a/src/api/notification/email-service.ts +++ b/src/api/notification/email-service.ts @@ -9,7 +9,7 @@ * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { logger } from '@countryconfig/logger' +import { logger, maskEmail } from '@countryconfig/logger' import * as Handlebars from 'handlebars' import * as nodemailer from 'nodemailer' import { @@ -50,12 +50,14 @@ export const sendEmail = async (params: { if (formattedParams.to.endsWith('@example.com')) { logger.info( - `Example email detected: ${formattedParams.to}. Not sending the email.` + `Example email detected: ${maskEmail( + formattedParams.to + )}. Not sending the email.` ) return } - logger.info(`Sending email to ${formattedParams.to}`) + logger.info(`Sending email to ${maskEmail(formattedParams.to)}`) const emailTransport = nodemailer.createTransport({ host: SMTP_HOST, @@ -77,7 +79,9 @@ export const sendEmail = async (params: { logger.error(`Unable to send mass email for error : ${error}`) } else { logger.error( - `Unable to send email to ${formattedParams.to} for error : ${error}` + `Unable to send email to ${maskEmail( + formattedParams.to + )} for error : ${error}` ) } diff --git a/src/api/notification/handler.ts b/src/api/notification/handler.ts index e28947d13..245a1f0ba 100644 --- a/src/api/notification/handler.ts +++ b/src/api/notification/handler.ts @@ -8,7 +8,7 @@ * * Copyright (C) The OpenCRVS Authors located at https://github.com/opencrvs/opencrvs-core/blob/master/AUTHORS. */ -import { logger } from '@countryconfig/logger' +import { logger, maskEmail, maskSms } from '@countryconfig/logger' import * as Hapi from '@hapi/hapi' import * as Joi from 'joi' import { IApplicationConfig, getApplicationConfig } from '../../utils' @@ -88,6 +88,14 @@ export async function notificationHandler( if (process.env.NODE_ENV !== 'production') { const { templateName, recipient, convertUnicode, type } = payload + if ('sms' in recipient) { + recipient.sms = maskSms(recipient.sms) + } else { + recipient.email = maskEmail(recipient.email) + recipient.bcc = Array.isArray(recipient.bcc) + ? recipient.bcc.map(maskEmail) + : undefined + } logger.info( `Ignoring notification due to NODE_ENV not being 'production'. Params: ${JSON.stringify( { @@ -103,7 +111,9 @@ export async function notificationHandler( if (isEmailPayload(applicationConfig, payload)) { const { templateName, variables, recipient } = payload - logger.info(`Notification method is email and recipient ${recipient.email}`) + logger.info( + `Notification method is email and recipient ${maskEmail(recipient.email)}` + ) const template = getTemplate(templateName.email) const emailSubject = @@ -161,7 +171,7 @@ export async function emailHandler( if (process.env.NODE_ENV !== 'production') { logger.info( `Ignoring email due to NODE_ENV not being 'production'. Params: ${JSON.stringify( - payload + { ...payload, from: maskEmail(payload.from), to: maskEmail(payload.to) } )}` ) return h.response().code(200) diff --git a/src/api/notification/sms-service.ts b/src/api/notification/sms-service.ts index caffde3bb..e071a8604 100644 --- a/src/api/notification/sms-service.ts +++ b/src/api/notification/sms-service.ts @@ -13,7 +13,7 @@ import { INFOBIP_GATEWAY_ENDPOINT, INFOBIP_SENDER_ID } from './constant' -import { logger } from '@countryconfig/logger' +import { logger, maskSms } from '@countryconfig/logger' import fetch from 'node-fetch' import * as Handlebars from 'handlebars' import { internal } from '@hapi/boom' @@ -81,9 +81,13 @@ export async function sendSMS( const responseBody = await response.text() if (!response.ok) { - logger.error(`Failed to send sms to ${recipient}. Reason: ${responseBody}`) + logger.error( + `Failed to send sms to ${maskSms(recipient)}. Reason: ${responseBody}` + ) throw internal( - `Failed to send notification to ${recipient}. Reason: ${responseBody}` + `Failed to send notification to ${maskSms( + recipient + )}. Reason: ${responseBody}` ) } logger.info(`Response from Infobip: ${JSON.stringify(responseBody)}`) diff --git a/src/logger.ts b/src/logger.ts index c17ff7774..55a694bce 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -11,7 +11,13 @@ import pino from 'pino' export const logger = process.env.NODE_ENV === 'production' - ? pino() + ? pino({ + redact: [ + 'req.headers.authorization', + 'req.remoteAddress', + "req.headers['x-real-ip']" + ] + }) : pino({ transport: { target: 'pino-pretty', @@ -25,3 +31,21 @@ export const logger = if (process.env.NODE_ENV === 'test') { logger.level = 'silent' } + +export function maskEmail(email: string) { + if (email.length <= 10) + return `${email.at(0)}${'*'.repeat(email.length - 2)}${email.at(-1)}` + + // The regex matches everything EXCEPT the first 3 and last 4 characters. + return email.replace(/(?<=.{3}).*(?=.{4})/, (match) => + '*'.repeat(match.length) + ) +} + +export function maskSms(sms: string) { + if (sms.length <= 8) + return `${sms.at(0)}${'*'.repeat(sms.length - 2)}${sms.at(-1)}` + + // The regex matches everything EXCEPT the first 3 and last 2 characters. + return sms.replace(/(?<=.{3}).*(?=.{2})/, (match) => '*'.repeat(match.length)) +} diff --git a/tsconfig.json b/tsconfig.json index e13e35794..05ab91cbb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,9 +2,7 @@ "compilerOptions": { "baseUrl": "./src", "paths": { - "@countryconfig/*": [ - "./*" - ] + "@countryconfig/*": ["./*"] }, "target": "es6", "module": "commonjs", @@ -12,12 +10,7 @@ "sourceMap": true, "moduleResolution": "node", "rootDir": ".", - "lib": [ - "esnext.asynciterable", - "es6", - "es2017", - "es2019" - ], + "lib": ["esnext.asynciterable", "es6", "es2017", "es2019", "es2022"], "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "noImplicitReturns": true, @@ -25,16 +18,9 @@ "noImplicitAny": true, "skipLibCheck": true, "strictNullChecks": true, - "types": [ - "fhir", - "geojson" - ] + "types": ["fhir", "geojson"] }, - "include": [ - "src/**/*.ts", - "infrastructure/environments/**/*.ts", - "typings" - ], + "include": ["src/**/*.ts", "infrastructure/environments/**/*.ts", "typings"], "exclude": [ "node_modules", "cypress", @@ -47,4 +33,4 @@ "ts-node": { "files": true } -} \ No newline at end of file +}