local-aws/src/audit/audit.interceptor.ts

112 lines
3.9 KiB
TypeScript

import { CallHandler, ExecutionContext, HttpException, Inject, Injectable, Logger, NestInterceptor, RequestTimeoutException } from '@nestjs/common';
import { randomUUID } from 'crypto';
import { catchError, Observable, tap, throwError } from 'rxjs';
import { Request as ExpressRequest, Response } from 'express';
import * as Joi from 'joi';
import { PrismaService } from '../_prisma/prisma.service';
import { ActionHandlers } from '../app.constants';
import { Action } from '../action.enum';
import { Format } from '../abstract-action.handler';
import { AwsException, InternalFailure } from '../aws-shared-entities/aws-exceptions';
import { IRequest, RequestContext } from '../_context/request.context';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AuditInterceptor<T> implements NestInterceptor<T, Response> {
private readonly logger = new Logger(AuditInterceptor.name);
constructor(
@Inject(ActionHandlers)
private readonly handlers: ActionHandlers,
private readonly prismaService: PrismaService,
private readonly configService: ConfigService,
) {}
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
const awsProperties = {
accountId: this.configService.get('AWS_ACCOUNT_ID'),
region: this.configService.get('AWS_REGION'),
host: `${this.configService.get('PROTO')}://${this.configService.get('HOST')}:${this.configService.get('PORT')}`,
};
const requestContext: RequestContext = {
requestId: randomUUID(),
awsProperties,
}
const httpContext = context.switchToHttp();
const request = httpContext.getRequest<IRequest>();
request.context = requestContext;
const hasTargetHeader = Object.keys(request.headers).some( k => k.toLocaleLowerCase() === 'x-amz-target');
const action = hasTargetHeader ? request.headers['x-amz-target'] : request.body.Action;
const { value: resolvedAction } = Joi.string().required().valid(...Object.values(Action)).validate(action) as { value: Action | undefined };
requestContext.action = resolvedAction;
const response = context.switchToHttp().getResponse<Response>();
response.header('x-amzn-RequestId', requestContext.requestId);
if (!resolvedAction || !this.handlers[resolvedAction]?.audit) {
return next.handle().pipe(
catchError(async (error: Error) => {
await this.prismaService.audit.create({
data: {
id: requestContext.requestId,
action,
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
response: JSON.stringify(error),
}
});
this.logger.error(error.message);
return error;
})
);
}
const handler = this.handlers[resolvedAction];
requestContext.format = handler.format;
return next.handle().pipe(
catchError((error: Error) => {
return throwError(() => {
if (error instanceof AwsException) {
return error;
}
const defaultError = new InternalFailure('Unexpected local AWS exception...');
this.logger.error(error.message);
defaultError.requestId = requestContext.requestId;
return defaultError;
});
}),
tap({
next: async (data) => await this.prismaService.audit.create({
data: {
id: requestContext.requestId,
action,
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
response: JSON.stringify(data),
}
}),
error: async (error) => await this.prismaService.audit.create({
data: {
id: requestContext.requestId,
action,
request: JSON.stringify({ __path: request.path, ...request.headers, ...request.body }),
response: JSON.stringify(error),
}
}),
})
);
}
}