@syngrisi/syngrisi
Version:
Syngrisi - Visual Testing Tool
99 lines (88 loc) • 3.48 kB
text/typescript
import { NextFunction, Request, Response } from 'express';
import { HttpStatus } from './httpStatus';
import { ZodError, ZodSchema } from 'zod';
import { ResponseStatus, ServiceResponse } from './ServiceResponse';
import log from '@logger';
import { LogOpts } from '@root/src/types';
import { errMsg } from './errMsg';
const logOpts: LogOpts = {
scope: 'validateRequests',
itemType: 'type',
msgType: 'VALIDATION',
};
const sensitiveKeys = new Set([
'password',
'currentpassword',
'newpassword',
'apikey',
'sso_client_secret',
'sso_cert',
'clientsecret',
'secret',
'token',
]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const sanitizeValueForLogging = (value: any): any => {
if (Array.isArray(value)) {
return value.map((item) => sanitizeValueForLogging(item));
}
if (value && typeof value === 'object') {
return Object.entries(value).reduce((acc, [key, val]) => {
if (sensitiveKeys.has(key.toLowerCase())) {
acc[key] = '[REDACTED]';
} else {
acc[key] = sanitizeValueForLogging(val);
}
return acc;
}, Array.isArray(value) ? [] : {} as Record<string, unknown>);
}
return value;
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function getReceivedValueFromRequest(request: { body: any; query: any; params: any }, path: (string | number)[]): any {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let currentValue: any = request;
path.forEach(segment => {
currentValue = currentValue[segment];
});
return currentValue;
}
export const validateRequest = (schema: ZodSchema, endpoint = '') => (req: Request, res: Response, next: NextFunction) => {
try {
const parsed = schema.parse({
body: req.body,
query: req.query,
params: req.params
});
if (parsed.body) req.body = parsed.body;
next();
} catch (err) {
if (err instanceof ZodError) {
// ZodError always has .errors property (array of ZodIssue)
// Use Array.isArray check to ensure we have a valid array
const zodErrors = Array.isArray(err.errors) ? err.errors : [];
if (zodErrors.length === 0) {
log.error(`ZodError with empty errors array! Raw error: ${JSON.stringify(err)}`, logOpts);
}
const sanitizedBody = sanitizeValueForLogging(req.body);
const sanitizedQuery = sanitizeValueForLogging(req.query);
const sanitizedParams = sanitizeValueForLogging(req.params);
const errors = zodErrors.map((e: any) => {
const receivedValue = getReceivedValueFromRequest(
{ body: sanitizedBody, query: sanitizedQuery, params: sanitizedParams },
e.path
);
return `\nError path: '${e.path.join('.')}': \nError ${e.message}, but received ${JSON.stringify(receivedValue)}`;
}).join(', ');
const errorMessage = ` ${endpoint ? '\nValidation error in the endpoint: "' + endpoint + '"' : ''}`
+ `${errors}, \nHTTP PROPERTIES:\n\tbody: ${JSON.stringify(sanitizedBody, null, '\t')}, `
+ `\n\tquery: ${JSON.stringify(sanitizedQuery, null, '\t')}, \n\tparams: ${JSON.stringify(sanitizedParams, null, '\t')}`;
const statusCode = HttpStatus.BAD_REQUEST;
log.error(errorMessage, logOpts);
res.status(statusCode).send(new ServiceResponse<null>(ResponseStatus.Failed, errorMessage, null, statusCode));
} else {
log.error(`Unexpected error: ${errMsg(err)}`, logOpts);
next(err);
}
}
};