BugFix: Proxy data, Logging with IP adresses

This commit is contained in:
Andreas Knuth 2024-09-20 18:28:43 +02:00
parent 178f2b4810
commit 860d30b16f
11 changed files with 86 additions and 29 deletions

View File

@ -19,6 +19,7 @@ import { EventModule } from './event/event.module';
import { JwtStrategy } from './jwt.strategy';
import { MailModule } from './mail/mail.module';
import { ContextService } from './context/context.service';
import { PaymentModule } from './payment/payment.module';
import { RequestDurationMiddleware } from './request-duration/request-duration.middleware';
import { SelectOptionsModule } from './select-options/select-options.module';
@ -63,7 +64,7 @@ console.log(JSON.stringify(process.env, null, 2));
EventModule,
],
controllers: [AppController, LogController],
providers: [AppService, FileService, JwtStrategy],
providers: [AppService, FileService, JwtStrategy, ContextService, ContextService],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {

View File

@ -1,10 +1,15 @@
import { createParamDecorator, ExecutionContext, Injectable } from '@nestjs/common';
import { RealIpInfo } from './models/main.model';
import { getRealIpInfo } from './utils/ip.util';
// export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => {
// const request = ctx.switchToHttp().getRequest();
// const ip = request.headers['cf-connecting-ip'] || request.headers['x-real-ip'] || request.headers['x-forwarded-for']?.split(',')[0] || request.connection.remoteAddress;
// const countryCode = request.headers['cf-ipcountry'];
// return { ip, countryCode };
// });
export const RealIp = createParamDecorator((data: unknown, ctx: ExecutionContext): RealIpInfo => {
const request = ctx.switchToHttp().getRequest();
const ip = request.headers['cf-connecting-ip'] || request.headers['x-real-ip'] || request.headers['x-forwarded-for']?.split(',')[0] || request.connection.remoteAddress;
const countryCode = request.headers['cf-ipcountry'];
return { ip, countryCode };
return getRealIpInfo(request);
});
@Injectable()
export class AppService {

View File

@ -0,0 +1,21 @@
// src/context/context.service.ts
import { Injectable } from '@nestjs/common';
import { AsyncLocalStorage } from 'async_hooks';
export interface RequestContext {
ip: string;
countryCode?: string;
}
@Injectable()
export class ContextService {
private readonly asyncLocalStorage = new AsyncLocalStorage<RequestContext>();
run(context: RequestContext, callback: () => void) {
this.asyncLocalStorage.run(context, callback);
}
getContext(): RequestContext | undefined {
return this.asyncLocalStorage.getStore();
}
}

View File

@ -3,16 +3,18 @@ import { ConfigService } from '@nestjs/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import pkg from 'pg';
import { ContextService, RequestContext } from 'src/context/context.service';
import { Logger } from 'winston';
import * as schema from './schema';
import { PG_CONNECTION } from './schema';
const { Pool } = pkg;
@Module({
providers: [
ContextService,
{
provide: PG_CONNECTION,
inject: [ConfigService, WINSTON_MODULE_PROVIDER],
useFactory: async (configService: ConfigService, logger: Logger) => {
inject: [ConfigService, WINSTON_MODULE_PROVIDER, ContextService],
useFactory: async (configService: ConfigService, logger: Logger, contextService: ContextService) => {
const connectionString = configService.get<string>('DATABASE_URL');
const pool = new Pool({
connectionString,
@ -21,7 +23,10 @@ const { Pool } = pkg;
// Definiere einen benutzerdefinierten Logger für Drizzle
const drizzleLogger = {
logQuery(query: string, params: unknown[]): void {
logger.info(query, params);
const context: RequestContext | undefined = contextService.getContext();
const ip = context?.ip || 'unknown';
const countryCode = context?.countryCode || 'unknown';
logger.info(`IP: ${ip} (${countryCode}) - Query: ${query} - Params: ${JSON.stringify(params)}`);
},
};

View File

@ -17,11 +17,9 @@ async function bootstrap() {
app.enableCors({
origin: '*',
//origin: 'http://localhost:4200', // Die URL Ihrer Angular-App
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
allowedHeaders: 'Content-Type, Accept, Authorization, x-hide-loading',
});
//origin: 'http://localhost:4200',
await app.listen(3000);
}
bootstrap();

View File

@ -1,27 +1,36 @@
import { Injectable, Logger, NestMiddleware } from '@nestjs/common';
import { NextFunction, Request, Response } from 'express';
import { ContextService } from 'src/context/context.service';
import { getRealIpInfo } from 'src/utils/ip.util';
@Injectable()
export class RequestDurationMiddleware implements NestMiddleware {
private readonly logger = new Logger(RequestDurationMiddleware.name);
constructor(private readonly contextService: ContextService) {}
use(req: Request, res: Response, next: NextFunction) {
const start = Date.now();
const clientIp = req.ip;
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${clientIp}`);
const { ip, countryCode } = getRealIpInfo(req);
res.on('finish', () => {
const duration = Date.now() - start;
let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${clientIp}`;
// Initialisieren des Kontextes für diese Anfrage
this.contextService.run({ ip, countryCode }, () => {
const start = Date.now();
if (req.method === 'POST' || req.method === 'PUT') {
const body = JSON.stringify(req.body);
logMessage += ` - Incoming Body: ${body}`;
}
this.logger.log(`Entering ${req.method} ${req.originalUrl} from ${ip}`);
this.logger.log(logMessage);
res.on('finish', () => {
const duration = Date.now() - start;
let logMessage = `${req.method} ${req.originalUrl} - ${duration}ms - IP: ${ip}`;
if (req.method === 'POST' || req.method === 'PUT') {
const body = JSON.stringify(req.body);
logMessage += ` - Incoming Body: ${body}`;
}
this.logger.log(logMessage);
});
next();
});
next();
}
}

View File

@ -0,0 +1,16 @@
import { Request } from 'express';
export interface RealIpInfo {
ip: string | undefined;
countryCode?: string;
}
export function getRealIpInfo(req: Request): RealIpInfo {
const ip =
(req.headers['cf-connecting-ip'] as string) ||
(req.headers['x-real-ip'] as string) ||
(typeof req.headers['x-forwarded-for'] === 'string' ? req.headers['x-forwarded-for'].split(',')[0] : req.connection.remoteAddress);
const countryCode = req.headers['cf-ipcountry'] as string;
return { ip, countryCode };
}

View File

@ -13,7 +13,7 @@ import { ListingsService } from '../../../services/listings.service';
import { SearchService } from '../../../services/search.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { UserService } from '../../../services/user.service';
import { getCriteriaStateObject } from '../../../utils/utils';
import { getCriteriaProxy } from '../../../utils/utils';
@UntilDestroy()
@Component({
selector: 'app-broker-listings',
@ -54,7 +54,7 @@ export class BrokerListingsComponent {
private route: ActivatedRoute,
private searchService: SearchService,
) {
this.criteria = getCriteriaStateObject('brokerListings');
this.criteria = getCriteriaProxy('brokerListings', this) as UserListingCriteria;
this.init();
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
if (criteria && criteria.criteriaType === 'brokerListings') {

View File

@ -12,7 +12,7 @@ import { ImageService } from '../../../services/image.service';
import { ListingsService } from '../../../services/listings.service';
import { SearchService } from '../../../services/search.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { getCriteriaStateObject } from '../../../utils/utils';
import { getCriteriaProxy } from '../../../utils/utils';
@UntilDestroy()
@Component({
selector: 'app-business-listings',
@ -50,7 +50,7 @@ export class BusinessListingsComponent {
private route: ActivatedRoute,
private searchService: SearchService,
) {
this.criteria = getCriteriaStateObject('businessListings');
this.criteria = getCriteriaProxy('businessListings', this) as BusinessListingCriteria;
this.init();
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
if (criteria && criteria.criteriaType === 'businessListings') {

View File

@ -12,7 +12,7 @@ import { ImageService } from '../../../services/image.service';
import { ListingsService } from '../../../services/listings.service';
import { SearchService } from '../../../services/search.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { getCriteriaStateObject } from '../../../utils/utils';
import { getCriteriaProxy } from '../../../utils/utils';
@UntilDestroy()
@Component({
selector: 'app-commercial-property-listings',
@ -49,7 +49,7 @@ export class CommercialPropertyListingsComponent {
private route: ActivatedRoute,
private searchService: SearchService,
) {
this.criteria = getCriteriaStateObject('commercialPropertyListings');
this.criteria = getCriteriaProxy('commercialPropertyListings', this) as CommercialPropertyListingCriteria;
this.init();
this.searchService.currentCriteria.pipe(untilDestroyed(this)).subscribe(criteria => {
if (criteria && criteria.criteriaType === 'commercialPropertyListings') {

View File

@ -335,6 +335,8 @@ export function createEnhancedProxy(obj: BusinessListingCriteria | CommercialPro
sessionStorageHandler.call(this, path, value, previous, applyData);
// Notify about the criteria change using the component's context
component.criteriaChangeService.notifyCriteriaChange();
if (component.criteriaChangeService) {
component.criteriaChangeService.notifyCriteriaChange();
}
});
}