refactor phase 4 hardening helpers and redacted logging
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
export const isNonEmptyString = (value) => {
|
||||||
|
return typeof value === "string" && value.trim().length > 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const sanitizeString = (value) => {
|
||||||
|
return typeof value === "string" ? value.trim() : "";
|
||||||
|
};
|
||||||
|
|
||||||
|
export const escapeODataString = (value) => {
|
||||||
|
return sanitizeString(value).replace(/'/g, "''");
|
||||||
|
};
|
||||||
+38
-6
@@ -1,5 +1,35 @@
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
|
||||||
|
const MASK = "[REDACTED]";
|
||||||
|
|
||||||
|
const redactString = (value) => {
|
||||||
|
if (typeof value !== "string") return value;
|
||||||
|
|
||||||
|
return value
|
||||||
|
.replace(
|
||||||
|
/([a-zA-Z0-9._%+-]{1,})@([a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g,
|
||||||
|
`${MASK}@${MASK}`
|
||||||
|
)
|
||||||
|
.replace(/Bearer\s+[A-Za-z0-9._\-]+/gi, `Bearer ${MASK}`)
|
||||||
|
.replace(/("client_secret"\s*:\s*")[^"]+("?)/gi, `$1${MASK}$2`)
|
||||||
|
.replace(/("access_token"\s*:\s*")[^"]+("?)/gi, `$1${MASK}$2`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const redactSensitive = (value) => {
|
||||||
|
if (value == null) return value;
|
||||||
|
|
||||||
|
if (typeof value === "string") {
|
||||||
|
return redactString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const json = JSON.stringify(value);
|
||||||
|
return redactString(json);
|
||||||
|
} catch (_error) {
|
||||||
|
return MASK;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const getIP = (req) => {
|
export const getIP = (req) => {
|
||||||
const forwarded = req.headers["x-forwarded-for"];
|
const forwarded = req.headers["x-forwarded-for"];
|
||||||
|
|
||||||
@@ -17,7 +47,7 @@ export const getIP = (req) => {
|
|||||||
export const consoleLogger = (err) => {
|
export const consoleLogger = (err) => {
|
||||||
console.log(
|
console.log(
|
||||||
"\n\n/////////////////////////////////////////////////\nRaw Error " +
|
"\n\n/////////////////////////////////////////////////\nRaw Error " +
|
||||||
err +
|
redactSensitive(err) +
|
||||||
"\n\n/////////////////////////////////////////////////\n"
|
"\n\n/////////////////////////////////////////////////\n"
|
||||||
);
|
);
|
||||||
var errStr =
|
var errStr =
|
||||||
@@ -40,22 +70,24 @@ export const consoleLogger = (err) => {
|
|||||||
"\n"
|
"\n"
|
||||||
: "") +
|
: "") +
|
||||||
(_.has(err, "response.data.error.message")
|
(_.has(err, "response.data.error.message")
|
||||||
? "\nMessage: " + err.response.data.error.message + "\n"
|
? "\nMessage: " +
|
||||||
|
redactSensitive(err.response.data.error.message) +
|
||||||
|
"\n"
|
||||||
: "") +
|
: "") +
|
||||||
(_.has(err, "response.config.url")
|
(_.has(err, "response.config.url")
|
||||||
? "\nRequest URL: " + err.config.url + "\n"
|
? "\nRequest URL: " + redactSensitive(err.config.url) + "\n"
|
||||||
: "") +
|
: "") +
|
||||||
(_.has(err, "response.headers.date")
|
(_.has(err, "response.headers.date")
|
||||||
? "\nRequest Time: " + err.response.headers.date + "\n"
|
? "\nRequest Time: " + err.response.headers.date + "\n"
|
||||||
: "") +
|
: "") +
|
||||||
(_.has(err, "config.url")
|
(_.has(err, "config.url")
|
||||||
? "\nAxios config url: " + err.config.url + "\n"
|
? "\nAxios config url: " + redactSensitive(err.config.url) + "\n"
|
||||||
: "") +
|
: "") +
|
||||||
(_.has(err, "config.url")
|
(_.has(err, "config.url")
|
||||||
? "\nAxios message url: " + err.message + "\n"
|
? "\nAxios message url: " + redactSensitive(err.message) + "\n"
|
||||||
: "") +
|
: "") +
|
||||||
(_.has(err, "config.data")
|
(_.has(err, "config.data")
|
||||||
? "\nRequest payload: " + err.config.data + "\n"
|
? "\nRequest payload: " + redactSensitive(err.config.data) + "\n"
|
||||||
: "") +
|
: "") +
|
||||||
"\n/////////////////////////////////////////////////\n";
|
"\n/////////////////////////////////////////////////\n";
|
||||||
|
|
||||||
|
|||||||
@@ -3,5 +3,6 @@ export * from "./core/logger";
|
|||||||
export * from "./core/hash";
|
export * from "./core/hash";
|
||||||
export * from "./core/token";
|
export * from "./core/token";
|
||||||
export * from "./core/headers";
|
export * from "./core/headers";
|
||||||
|
export * from "./core/guards";
|
||||||
|
|
||||||
export * from "./services";
|
export * from "./services";
|
||||||
|
|||||||
@@ -25,11 +25,19 @@
|
|||||||
* description: Failed
|
* description: Failed
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { consoleLogger } from "../../../actions/core/logger";
|
import { consoleLogger, redactSensitive } from "../../../actions/core/logger";
|
||||||
|
import { isNonEmptyString, sanitizeString } from "../../../actions/core/guards";
|
||||||
import { getPreferredLanguage } from "../../../actions/services/accountService";
|
import { getPreferredLanguage } from "../../../actions/services/accountService";
|
||||||
|
|
||||||
export default async function ApiProxy(req, res) {
|
export default async function ApiProxy(req, res) {
|
||||||
var data = req.body;
|
var data = req.body;
|
||||||
|
const emailAddress = sanitizeString(data?.emailAddress);
|
||||||
|
|
||||||
|
if (!isNonEmptyString(emailAddress)) {
|
||||||
|
return res.status(400).json({ error: "emailAddress is required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
data.emailAddress = emailAddress;
|
||||||
|
|
||||||
if (data?.reference === "PEDW-NEW-CASEREF") {
|
if (data?.reference === "PEDW-NEW-CASEREF") {
|
||||||
try {
|
try {
|
||||||
@@ -50,7 +58,10 @@ export default async function ApiProxy(req, res) {
|
|||||||
const notifyClient = new NotifyClient(process.env.NOTIFY_API_KEY);
|
const notifyClient = new NotifyClient(process.env.NOTIFY_API_KEY);
|
||||||
//const emailReplyToId = process.env.EMAIL_REPLY_TO_ID;
|
//const emailReplyToId = process.env.EMAIL_REPLY_TO_ID;
|
||||||
|
|
||||||
console.log("///////////////\n Sending email \ns//////////////", data);
|
console.log(
|
||||||
|
"///////////////\n Sending email \ns//////////////",
|
||||||
|
redactSensitive(data)
|
||||||
|
);
|
||||||
|
|
||||||
notifyClient
|
notifyClient
|
||||||
.sendEmail(data.templateId, data.emailAddress, {
|
.sendEmail(data.templateId, data.emailAddress, {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import CryptoJS from "crypto-js";
|
import CryptoJS from "crypto-js";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { isNonEmptyString, sanitizeString } from "../../../actions/core/guards";
|
||||||
|
import { consoleLogger } from "../../../actions/core/logger";
|
||||||
import { getToken } from "../../../actions/core/token";
|
import { getToken } from "../../../actions/core/token";
|
||||||
import { getCaseBlob } from "../../../actions/azurestorage";
|
import { getCaseBlob } from "../../../actions/azurestorage";
|
||||||
const WORDKEY = process.env.HASHKEY;
|
const WORDKEY = process.env.HASHKEY;
|
||||||
@@ -66,6 +68,17 @@ const hashAPIPath = (queryPath) => {
|
|||||||
|
|
||||||
export default async function ApiProxy(req, res) {
|
export default async function ApiProxy(req, res) {
|
||||||
var createTaskBody = req.body;
|
var createTaskBody = req.body;
|
||||||
|
|
||||||
|
const contactEmail = sanitizeString(createTaskBody?.contactEmail);
|
||||||
|
const contactSubject = sanitizeString(createTaskBody?.contactSubject);
|
||||||
|
const contactBody = sanitizeString(createTaskBody?.contactbody);
|
||||||
|
|
||||||
|
if (!isNonEmptyString(contactEmail) || !isNonEmptyString(contactSubject)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
error: "contactEmail and contactSubject are required"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var queryUrl = "tasks";
|
var queryUrl = "tasks";
|
||||||
var token = await getToken();
|
var token = await getToken();
|
||||||
|
|
||||||
@@ -87,8 +100,8 @@ export default async function ApiProxy(req, res) {
|
|||||||
let teamId = teamMap[contactValue];
|
let teamId = teamMap[contactValue];
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
"subject": `Contact Us Enquiry - ${createTaskBody.contactSubject}`,
|
"subject": `Contact Us Enquiry - ${contactSubject}`,
|
||||||
"description": `From: ${createTaskBody.contactEmail}\n\n${createTaskBody.contactbody}`,
|
"description": `From: ${contactEmail}\n\n${contactBody}`,
|
||||||
"scheduledstart": new Date().toISOString(),
|
"scheduledstart": new Date().toISOString(),
|
||||||
"scheduledend": new Date(
|
"scheduledend": new Date(
|
||||||
new Date().getTime() + 60 * 60000 * 24
|
new Date().getTime() + 60 * 60000 * 24
|
||||||
@@ -118,10 +131,7 @@ export default async function ApiProxy(req, res) {
|
|||||||
const { data } = await axios(config);
|
const { data } = await axios(config);
|
||||||
return res.status(200).json(data);
|
return res.status(200).json(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
consoleLogger(error);
|
||||||
"CRM Task Creation Error:",
|
|
||||||
error.response?.data || error
|
|
||||||
);
|
|
||||||
return res
|
return res
|
||||||
.status(400)
|
.status(400)
|
||||||
.json({ error: "Failed to create CRM task", details: error });
|
.json({ error: "Failed to create CRM task", details: error });
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ import axios from "axios";
|
|||||||
import CryptoJS from "crypto-js";
|
import CryptoJS from "crypto-js";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { azureHeaders } from "../../../actions/core/headers";
|
import { azureHeaders } from "../../../actions/core/headers";
|
||||||
import { consoleLogger } from "../../../actions/core/logger";
|
import {
|
||||||
|
escapeODataString,
|
||||||
|
isNonEmptyString,
|
||||||
|
sanitizeString
|
||||||
|
} from "../../../actions/core/guards";
|
||||||
|
import { consoleLogger, redactSensitive } from "../../../actions/core/logger";
|
||||||
import { getToken } from "../../../actions/core/token";
|
import { getToken } from "../../../actions/core/token";
|
||||||
|
|
||||||
const WORDKEY = process.env.HASHKEY;
|
const WORDKEY = process.env.HASHKEY;
|
||||||
@@ -38,15 +43,19 @@ const hashAPIPath = (queryPath) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default async function ApiProxy(req, res) {
|
export default async function ApiProxy(req, res) {
|
||||||
var emailAddress = req.query.emailAddress;
|
var emailAddress = sanitizeString(req.query.emailAddress);
|
||||||
var token = await getToken();
|
var token = await getToken();
|
||||||
|
|
||||||
|
if (!isNonEmptyString(emailAddress)) {
|
||||||
|
return res.status(400).json({ error: "emailAddress is required" });
|
||||||
|
}
|
||||||
|
|
||||||
var queryUrl =
|
var queryUrl =
|
||||||
"contacts?$filter=emailaddress1 eq '" +
|
"contacts?$filter=emailaddress1 eq '" +
|
||||||
emailAddress +
|
escapeODataString(emailAddress) +
|
||||||
"'&$count=true&$select=pinswg_preferredlanguage,contactid";
|
"'&$count=true&$select=pinswg_preferredlanguage,contactid";
|
||||||
|
|
||||||
console.log(queryUrl);
|
console.log(redactSensitive(queryUrl));
|
||||||
// var queryUrl = "contacts/?$count=true&$select=emailaddress1, contactid";
|
// var queryUrl = "contacts/?$count=true&$select=emailaddress1, contactid";
|
||||||
|
|
||||||
var apiResponse = _.isEmpty(req.query)
|
var apiResponse = _.isEmpty(req.query)
|
||||||
|
|||||||
Reference in New Issue
Block a user