refactor: atualizações e remoção de módulos não utilizados
This commit is contained in:
@@ -7,7 +7,8 @@ import { ConfigService } from '@nestjs/config';
|
||||
export class RateLimiterMiddleware implements NestMiddleware {
|
||||
private readonly ttl: number;
|
||||
private readonly limit: number;
|
||||
private readonly store: Map<string, { count: number; expiration: number }> = new Map();
|
||||
private readonly store: Map<string, { count: number; expiration: number }> =
|
||||
new Map();
|
||||
|
||||
constructor(private configService: ConfigService) {
|
||||
this.ttl = this.configService.get<number>('THROTTLE_TTL', 60);
|
||||
@@ -22,7 +23,7 @@ export class RateLimiterMiddleware implements NestMiddleware {
|
||||
|
||||
const key = this.generateKey(req);
|
||||
const now = Date.now();
|
||||
|
||||
|
||||
if (!this.store.has(key)) {
|
||||
this.store.set(key, { count: 1, expiration: now + this.ttl * 1000 });
|
||||
this.setRateLimitHeaders(res, 1);
|
||||
@@ -42,7 +43,9 @@ export class RateLimiterMiddleware implements NestMiddleware {
|
||||
const timeToWait = Math.ceil((record.expiration - now) / 1000);
|
||||
this.setRateLimitHeaders(res, record.count);
|
||||
res.header('Retry-After', String(timeToWait));
|
||||
throw new ThrottlerException(`Too Many Requests. Retry after ${timeToWait} seconds.`);
|
||||
throw new ThrottlerException(
|
||||
`Too Many Requests. Retry after ${timeToWait} seconds.`,
|
||||
);
|
||||
}
|
||||
|
||||
record.count++;
|
||||
@@ -52,13 +55,17 @@ export class RateLimiterMiddleware implements NestMiddleware {
|
||||
|
||||
private generateKey(req: Request): string {
|
||||
// Combina IP com rota para rate limiting mais preciso
|
||||
const ip = req.ip || req.headers['x-forwarded-for'] as string || 'unknown-ip';
|
||||
const ip =
|
||||
req.ip || (req.headers['x-forwarded-for'] as string) || 'unknown-ip';
|
||||
const path = req.path || req.originalUrl || '';
|
||||
return `${ip}:${path}`;
|
||||
}
|
||||
|
||||
private setRateLimitHeaders(res: Response, count: number): void {
|
||||
res.header('X-RateLimit-Limit', String(this.limit));
|
||||
res.header('X-RateLimit-Remaining', String(Math.max(0, this.limit - count)));
|
||||
res.header(
|
||||
'X-RateLimit-Remaining',
|
||||
String(Math.max(0, this.limit - count)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,20 +7,20 @@ export class RequestSanitizerMiddleware implements NestMiddleware {
|
||||
if (req.headers) {
|
||||
this.sanitizeObject(req.headers);
|
||||
}
|
||||
|
||||
|
||||
if (req.query) {
|
||||
this.sanitizeObject(req.query);
|
||||
}
|
||||
|
||||
|
||||
if (req.body) {
|
||||
this.sanitizeObject(req.body);
|
||||
}
|
||||
|
||||
|
||||
next();
|
||||
}
|
||||
|
||||
private sanitizeObject(obj: any) {
|
||||
Object.keys(obj).forEach(key => {
|
||||
Object.keys(obj).forEach((key) => {
|
||||
if (typeof obj[key] === 'string') {
|
||||
obj[key] = this.sanitizeString(obj[key]);
|
||||
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
@@ -32,17 +32,17 @@ export class RequestSanitizerMiddleware implements NestMiddleware {
|
||||
private sanitizeString(str: string): string {
|
||||
// Remover tags HTML básicas
|
||||
str = str.replace(/<(|\/|[^>\/bi]|\/[^>bi]|[^\/>][^>]+|\/[^>][^>]+)>/g, '');
|
||||
|
||||
|
||||
// Remover scripts JavaScript
|
||||
str = str.replace(/javascript:/g, '');
|
||||
str = str.replace(/on\w+=/g, '');
|
||||
|
||||
|
||||
// Remover comentários HTML
|
||||
str = str.replace(/<!--[\s\S]*?-->/g, '');
|
||||
|
||||
|
||||
// Sanitizar caracteres especiais para evitar SQL injection
|
||||
str = str.replace(/'/g, "''");
|
||||
|
||||
|
||||
return str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,25 @@
|
||||
import {
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ResultModel } from '../shared/ResultModel';
|
||||
|
||||
@Injectable()
|
||||
export class ResponseInterceptor<T> implements NestInterceptor<T, ResultModel<T>> {
|
||||
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<ResultModel<T>> {
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
return ResultModel.success(data);
|
||||
}),
|
||||
);
|
||||
}
|
||||
CallHandler,
|
||||
ExecutionContext,
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { ResultModel } from '../shared/ResultModel';
|
||||
|
||||
@Injectable()
|
||||
export class ResponseInterceptor<T>
|
||||
implements NestInterceptor<T, ResultModel<T>>
|
||||
{
|
||||
intercept(
|
||||
context: ExecutionContext,
|
||||
next: CallHandler<T>,
|
||||
): Observable<ResultModel<T>> {
|
||||
return next.handle().pipe(
|
||||
map((data) => {
|
||||
return ResultModel.success(data);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { registerDecorator, ValidationOptions, ValidationArguments } from 'class-validator';
|
||||
import {
|
||||
registerDecorator,
|
||||
ValidationOptions,
|
||||
ValidationArguments,
|
||||
} from 'class-validator';
|
||||
|
||||
// Decorator para sanitizar strings e prevenir SQL/NoSQL injection
|
||||
export function IsSanitized(validationOptions?: ValidationOptions) {
|
||||
return function (object: Object, propertyName: string) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isSanitized',
|
||||
target: object.constructor,
|
||||
@@ -11,24 +15,27 @@ export function IsSanitized(validationOptions?: ValidationOptions) {
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
if (typeof value !== 'string') return true; // Skip non-string values
|
||||
|
||||
const sqlInjectionRegex = /('|"|;|--|\/\*|\*\/|@@|@|char|nchar|varchar|nvarchar|alter|begin|cast|create|cursor|declare|delete|drop|end|exec|execute|fetch|insert|kill|open|select|sys|sysobjects|syscolumns|table|update|xp_)/i;
|
||||
|
||||
const sqlInjectionRegex =
|
||||
/('|"|;|--|\/\*|\*\/|@@|@|char|nchar|varchar|nvarchar|alter|begin|cast|create|cursor|declare|delete|drop|end|exec|execute|fetch|insert|kill|open|select|sys|sysobjects|syscolumns|table|update|xp_)/i;
|
||||
if (sqlInjectionRegex.test(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check for NoSQL injection patterns (MongoDB)
|
||||
const noSqlInjectionRegex = /(\$where|\$ne|\$gt|\$lt|\$gte|\$lte|\$in|\$nin|\$or|\$and|\$regex|\$options|\$elemMatch|\{.*\:.*\})/i;
|
||||
const noSqlInjectionRegex =
|
||||
/(\$where|\$ne|\$gt|\$lt|\$gte|\$lte|\$in|\$nin|\$or|\$and|\$regex|\$options|\$elemMatch|\{.*\:.*\})/i;
|
||||
if (noSqlInjectionRegex.test(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
// Check for XSS attempts
|
||||
const xssRegex = /(<script|javascript:|on\w+\s*=|<%=|<img|<iframe|alert\(|window\.|document\.)/i;
|
||||
const xssRegex =
|
||||
/(<script|javascript:|on\w+\s*=|<%=|<img|<iframe|alert\(|window\.|document\.)/i;
|
||||
if (xssRegex.test(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
return true;
|
||||
},
|
||||
defaultMessage(args: ValidationArguments) {
|
||||
@@ -41,7 +48,7 @@ export function IsSanitized(validationOptions?: ValidationOptions) {
|
||||
|
||||
// Decorator para validar IDs seguros (evita injeção em IDs)
|
||||
export function IsSecureId(validationOptions?: ValidationOptions) {
|
||||
return function (object: Object, propertyName: string) {
|
||||
return function (object: object, propertyName: string) {
|
||||
registerDecorator({
|
||||
name: 'isSecureId',
|
||||
target: object.constructor,
|
||||
@@ -49,13 +56,16 @@ export function IsSecureId(validationOptions?: ValidationOptions) {
|
||||
options: validationOptions,
|
||||
validator: {
|
||||
validate(value: any, args: ValidationArguments) {
|
||||
if (typeof value !== 'string' && typeof value !== 'number') return false;
|
||||
|
||||
if (typeof value !== 'string' && typeof value !== 'number')
|
||||
return false;
|
||||
|
||||
if (typeof value === 'string') {
|
||||
// Permitir apenas: letras, números, hífens, underscores e GUIDs
|
||||
return /^[a-zA-Z0-9\-_]+$|^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
||||
return /^[a-zA-Z0-9\-_]+$|^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Se for número, deve ser positivo
|
||||
return value > 0;
|
||||
},
|
||||
@@ -65,4 +75,4 @@ export function IsSecureId(validationOptions?: ValidationOptions) {
|
||||
},
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user