Sts addition, kms updates, context object, improved exception handling
This commit is contained in:
102
src/_context/audit.interceptor.ts
Normal file
102
src/_context/audit.interceptor.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
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 './request.context';
|
||||
|
||||
|
||||
@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,
|
||||
) {}
|
||||
|
||||
intercept(context: ExecutionContext, next: CallHandler<T>): Observable<any> {
|
||||
|
||||
const requestContext: RequestContext = {
|
||||
requestId: randomUUID(),
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}),
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
25
src/_context/exception.filter.ts
Normal file
25
src/_context/exception.filter.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { ArgumentsHost, Catch, ExceptionFilter } from "@nestjs/common";
|
||||
import { Response } from 'express';
|
||||
|
||||
import { AwsException } from "../aws-shared-entities/aws-exceptions";
|
||||
import { IRequest } from "./request.context";
|
||||
import { Format } from "../abstract-action.handler";
|
||||
|
||||
@Catch(AwsException)
|
||||
export class AwsExceptionFilter implements ExceptionFilter {
|
||||
catch(exception: AwsException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const request = ctx.getRequest<IRequest>();
|
||||
const response = ctx.getResponse<Response>();
|
||||
|
||||
exception.requestId = request.context.requestId;
|
||||
|
||||
if (request.context.format === Format.Xml) {
|
||||
const xml = exception.toXml();
|
||||
return response.status(exception.statusCode).send(xml);
|
||||
}
|
||||
const [newError, newHeaders] = exception.toJson();
|
||||
response.setHeaders(new Map(Object.entries(newHeaders)));
|
||||
return response.status(exception.statusCode).json(newError.getResponse());
|
||||
}
|
||||
}
|
||||
20
src/_context/request.context.ts
Normal file
20
src/_context/request.context.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Request } from "express";
|
||||
|
||||
import { Action } from "../action.enum";
|
||||
import { Format } from "../abstract-action.handler";
|
||||
|
||||
export interface RequestContext {
|
||||
action?: Action;
|
||||
format?: Format;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
export interface IRequest extends Request {
|
||||
context: RequestContext;
|
||||
headers: {
|
||||
'x-amz-target'?: string;
|
||||
},
|
||||
body: {
|
||||
'Action'?: string;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user