- Fix DI issues: circular MCP module dependency, EventBus type import, SearchModule provider, CacheService metric counters placement - Fix Express 5 readonly req.query in SanitizeInputMiddleware - Fix Typesense client lazy initialization (getter instead of constructor) - Fix MinIO bucket init error handling (non-fatal on 403) - Fix missing class-validator decorators on bigint DTO fields (priceVND, amountVND) - Fix subscription plan 404 (was returning 500 for invalid tier) - Disable CSRF and raise rate limits in test environment - Update E2E tests to match actual API response shapes - Update CI workflow with Redis, Typesense, MinIO services and env vars All 101 API E2E tests now pass against Docker dev environment. Co-Authored-By: Paperclip <noreply@paperclip.ing>
55 lines
1.7 KiB
TypeScript
55 lines
1.7 KiB
TypeScript
import { Injectable, type NestMiddleware } from '@nestjs/common';
|
|
import type { NextFunction, Request, Response } from 'express';
|
|
import sanitizeHtml from 'sanitize-html';
|
|
|
|
const SANITIZE_OPTIONS: sanitizeHtml.IOptions = {
|
|
allowedTags: [],
|
|
allowedAttributes: {},
|
|
disallowedTagsMode: 'recursiveEscape',
|
|
};
|
|
|
|
function sanitizeValue(value: unknown): unknown {
|
|
if (typeof value === 'string') {
|
|
return sanitizeHtml(value, SANITIZE_OPTIONS);
|
|
}
|
|
if (Array.isArray(value)) {
|
|
return value.map(sanitizeValue);
|
|
}
|
|
if (value !== null && typeof value === 'object') {
|
|
return sanitizeObject(value as Record<string, unknown>);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
function sanitizeObject(obj: Record<string, unknown>): Record<string, unknown> {
|
|
const sanitized: Record<string, unknown> = {};
|
|
for (const [key, val] of Object.entries(obj)) {
|
|
sanitized[key] = sanitizeValue(val);
|
|
}
|
|
return sanitized;
|
|
}
|
|
|
|
/**
|
|
* Strips HTML tags from all string values in request body, query, and params
|
|
* to prevent stored XSS attacks.
|
|
*/
|
|
@Injectable()
|
|
export class SanitizeInputMiddleware implements NestMiddleware {
|
|
use(req: Request, _res: Response, next: NextFunction): void {
|
|
if (req.body && typeof req.body === 'object') {
|
|
req.body = sanitizeObject(req.body as Record<string, unknown>);
|
|
}
|
|
if (req.query && typeof req.query === 'object') {
|
|
for (const [key, val] of Object.entries(req.query)) {
|
|
(req.query as Record<string, unknown>)[key] = sanitizeValue(val);
|
|
}
|
|
}
|
|
if (req.params && typeof req.params === 'object') {
|
|
for (const [key, val] of Object.entries(req.params)) {
|
|
(req.params as Record<string, unknown>)[key] = sanitizeValue(val);
|
|
}
|
|
}
|
|
next();
|
|
}
|
|
}
|