UNPKG

@ahmedhegazee/nestjs-telescope

Version:

Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling

233 lines 9.32 kB
"use strict"; var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __metadata = (this && this.__metadata) || function (k, v) { if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); }; var RequestWatcherInterceptor_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.RequestWatcherInterceptor = void 0; const common_1 = require("@nestjs/common"); const operators_1 = require("rxjs/operators"); const request_watcher_service_1 = require("./request-watcher.service"); let RequestWatcherInterceptor = RequestWatcherInterceptor_1 = class RequestWatcherInterceptor { constructor(requestWatcher) { this.requestWatcher = requestWatcher; this.logger = new common_1.Logger(RequestWatcherInterceptor_1.name); } intercept(context, next) { const request = context.switchToHttp().getRequest(); const response = context.switchToHttp().getResponse(); if (!this.shouldTrackRequest(request)) { return next.handle(); } const requestContext = this.createRequestContext(request); return next.handle().pipe((0, operators_1.tap)(data => { const responseContext = this.createResponseContext(response, data, requestContext); this.trackRequest(requestContext, responseContext, null); }), (0, operators_1.catchError)(error => { const responseContext = this.createResponseContext(response, null, requestContext); this.trackRequest(requestContext, responseContext, error); throw error; })); } shouldTrackRequest(request) { const excludedPaths = this.requestWatcher.getExcludedPaths(); const path = request.path; if (excludedPaths.some(excluded => path.startsWith(excluded))) { return false; } return this.requestWatcher.shouldSampleRequest(request); } createRequestContext(request) { const startTime = Date.now(); const id = this.generateRequestId(); return { id, startTime, method: request.method, url: request.originalUrl || request.url, headers: this.sanitizeHeaders(request.headers), query: this.sanitizeQuery(request.query), body: this.sanitizeBody(request.body), userAgent: request.get('user-agent') || '', ip: this.getClientIp(request), sessionId: this.extractSessionId(request), userId: this.extractUserId(request), traceId: this.extractTraceId(request) }; } createResponseContext(response, data, requestContext) { const endTime = Date.now(); const duration = endTime - requestContext.startTime; return { statusCode: response.statusCode, headers: this.sanitizeHeaders(response.getHeaders()), body: this.sanitizeResponseBody(data, response.statusCode), size: this.calculateResponseSize(data, response), endTime, duration }; } trackRequest(requestContext, responseContext, error) { try { this.requestWatcher.trackRequest(requestContext, responseContext, error); } catch (trackingError) { this.logger.error('Failed to track request:', trackingError); } } sanitizeHeaders(headers) { const sanitized = {}; const sensitiveHeaders = [ 'authorization', 'cookie', 'x-api-key', 'x-auth-token', 'x-access-token', 'x-csrf-token' ]; for (const [key, value] of Object.entries(headers)) { const lowerKey = key.toLowerCase(); if (sensitiveHeaders.includes(lowerKey)) { sanitized[key] = '[REDACTED]'; } else { sanitized[key] = Array.isArray(value) ? value.join(', ') : String(value); } } return sanitized; } sanitizeQuery(query) { if (!query || typeof query !== 'object') { return {}; } const sanitized = {}; const sensitiveKeys = ['password', 'token', 'secret', 'key', 'auth']; for (const [key, value] of Object.entries(query)) { const lowerKey = key.toLowerCase(); if (sensitiveKeys.some(sensitive => lowerKey.includes(sensitive))) { sanitized[key] = '[REDACTED]'; } else { sanitized[key] = value; } } return sanitized; } sanitizeBody(body) { if (!body) { return null; } if (this.requestWatcher.shouldMaskBody(body)) { return '[MASKED]'; } if (typeof body === 'object' && body !== null) { const sanitized = Array.isArray(body) ? [] : {}; const sensitiveKeys = ['password', 'token', 'secret', 'key', 'auth', 'credit', 'ssn']; for (const [key, value] of Object.entries(body)) { const lowerKey = key.toLowerCase(); if (sensitiveKeys.some(sensitive => lowerKey.includes(sensitive))) { sanitized[key] = '[REDACTED]'; } else { sanitized[key] = typeof value === 'object' ? this.sanitizeBody(value) : value; } } return sanitized; } return body; } sanitizeResponseBody(data, statusCode) { if (statusCode >= 200 && statusCode < 300) { return this.requestWatcher.shouldLogSuccessfulResponseBodies() ? data : null; } if (statusCode >= 400) { return this.sanitizeBody(data); } return data; } calculateResponseSize(data, response) { if (data && typeof data === 'object') { try { return JSON.stringify(data).length; } catch { return 0; } } if (typeof data === 'string') { return data.length; } const contentLength = response.get('content-length'); return contentLength ? parseInt(contentLength, 10) : 0; } getClientIp(request) { return (request.ip || request.headers['x-forwarded-for'] || request.headers['x-real-ip'] || request.connection?.remoteAddress || 'unknown'); } extractSessionId(request) { const sessionCookie = request.cookies?.sessionId; if (sessionCookie) { return sessionCookie; } const sessionHeader = request.headers['x-session-id']; if (sessionHeader) { return Array.isArray(sessionHeader) ? sessionHeader[0] : sessionHeader; } const authHeader = request.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { try { const token = authHeader.substring(7); const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()); return payload.sessionId || payload.jti; } catch { } } return undefined; } extractUserId(request) { const user = request.user; if (user) { return user.id || user.userId || user.sub; } const authHeader = request.headers.authorization; if (authHeader && authHeader.startsWith('Bearer ')) { try { const token = authHeader.substring(7); const payload = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()); return payload.userId || payload.sub || payload.id; } catch { } } return undefined; } extractTraceId(request) { const traceHeader = request.headers['x-trace-id'] || request.headers['x-request-id']; if (traceHeader) { return Array.isArray(traceHeader) ? traceHeader[0] : traceHeader; } return this.generateTraceId(); } generateRequestId() { return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } generateTraceId() { return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } }; exports.RequestWatcherInterceptor = RequestWatcherInterceptor; exports.RequestWatcherInterceptor = RequestWatcherInterceptor = RequestWatcherInterceptor_1 = __decorate([ (0, common_1.Injectable)(), __metadata("design:paramtypes", [request_watcher_service_1.RequestWatcherService]) ], RequestWatcherInterceptor); //# sourceMappingURL=request-watcher.interceptor.js.map