How to execute pipe(ValidateObjectId) before guard(ResourceOwnerGuard)? - node.js

Im playing around with nestjs and mongoose.
The code:
class BrevesController {
constructor(private readonly brevesService: BrevesService) { }
// Here is used BreveOwnerGuard(1)
#UseGuards(JwtAuthGuard, BreveOwnerGuard)
#Get(':breveId')
// Here is used ValidateObjectId(3)
async getById(#Param('breveId', ValidateObjectId) id: string) {
return await this.brevesService.getById(id)
}
}
class BreveOwnerGuard {
constructor(private readonly brevesService: BrevesService) { }
async canActivate(context: ExecutionContext) {
const req = context.switchToHttp().getRequest()
const {user, params} = req
const {breveId} = params
// This is executed before ValidateObjectId in getById
// route handler and unknown error is thrown but we
// have pipe for this.(2)
const breve = await this.brevesService.getById(breveId)
const breveCreatorId = breve.creatorId.toString()
const userId = user.id
return breveCreatorId === userId
}
}
So after request /breves/:breveId with invalid object id, the BreveOwnerGuard is executed before ValidateObjectId and unknown error is thrown.
Is there a way for this flow to validate the ObjectId before BreveOwnerGuard ?
Or what should I do in this case? What is expected ?

Guards are executed after each middleware, but before any interceptor or pipe.
Source: Guard Docs (emphasis by me)
Not much you can do other than change the ResourceOwnerGuard to a pipe or the ValidateObjectId into a Guard.

Related

How to access parent query arguments in GraphQL and Nestjs execution context

Let's say we have a bookshop and an author entity, to show the author their earnings stat, we want to check if the authenticated user is indeed the author themselves. So we have:
#UseGuards(GqlAuthGuard)
#ResolveField(() => [Eearning], { name: 'earnings' })
async getEarnings(
#Parent() author: Author,
#GqlUser() user: User,
) {
if (user.id !== author.id)
throw new UnauthorizedException(
'Each author can only view their own data',
);
// rest of the function implementation
}
We could query this:
query {
author(id: "2bd79-6d7f-76a332b06b") {
earnings {
sells
}
}
}
Now imagine we want to use a custom Guard instead of that if statement. Something like below:
#Injectable()
export class AutherGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const ctx = GqlExecutionContext.create(context);
// const artistId = ?
}
}
How can I access the id argument given to the author query when AutherGuard is used for the getEarnings handler?
Not sure how documented is that but the parent object can be accessed through the getRoot method:
const gqlContext = GqlExecutionContext.create(context);
const root = gqlContext.getRoot();
const authorId = root.id;
In fact, we have a helper function that we use like this:
export function getArgs(context: ExecutionContext): any {
if (context.getType<GqlContextType>() === "graphql") {
const gqlContext = GqlExecutionContext.create(context);
return { ...gqlContext.getArgs(), $parent: gqlContext.getRoot() };
} else if (context.getType() === "http") {
return context.switchToHttp().getRequest().params;
}
}
...
const args = getArgs(context);
const authorId = _.get(args, "$parent.id");

NestJS/GraphQL/Local Auth - How to define variables passed in

What is the standard approach to defining variables to be used only in guards/interceptors?
Resolver
Without the #Args() it throws an error when I send a mutation with variables
With the #Args(), it has an eslint error createUserInput' is defined but never used
#UseGuards(GqlLocalAuthGuard)
#Mutation(() => User)
async login(
// #Args("createUserInput") createUserInput: CreateUserInput,
// ^ it works if I put this
): Promise<User> {
return { id: "1", email: "fake#gmail.com" };
}
GqlLocalAuthGuard
This extends local auth guard and the strategy verifies that the user put in the correct password
export class GqlLocalAuthGuard extends AuthGuard("local") {
getRequest(context: ExecutionContext): any {
const ctx = GqlExecutionContext.create(context);
const req = ctx.getContext().req;
const { createUserInput } = ctx.getArgs<any>();
req.body = createUserInput;
return req;
}
}
Error without Args()
"GraphQLError: Unknown argument \"createUserInput\" on field \"Mutation.login\"."
Mutation
mutation ($createUserInput: CreateUserInput!) {
login (createUserInput: $createUserInput) {
id,
email,
}
}

Intercepting in Multer Mutates Request? (NestJS)

Does multer mutates any request that has given to it? I'm currently trying to intercept the request to add this in logs.
But whenever I try to execute this code first:
const newReq = cloneDeep(request); // lodash cloneDeep
const newRes = cloneDeep(response);
const postMulterRequest: any = await new Promise((resolve, reject) => {
const multerReponse = multer().any()
multerReponse(request, newRes, err => {
if (err) reject(err)
resolve(request)
})
})
files = postMulterRequest?.files;
The #UseInterceptors(FileInterceptor('file')) becomes undefined.
I have already seen the problem, it seems like the multerReponse(request, newRes, err => { mutates the request. But I don't know what the other approach I can do to fix this. (I tried JSON Serialization, Object.assign, cloneDeep, but none of those worked)
I have tried adding newReq and newRes (cloned object) to multerResponse at first it worked. But at the second time, the thread only hangs up, and doesn't proceed to next steps. Or the multerReponse(request, newRes, err => { doesn't return anything.
The whole code looks like this and used globally (some parts of here were redacted/removed; but the main logic is still the same) :
#Injectable()
export class AuditingInterceptor implements NestInterceptor {
constructor(
#InjectModel(Auditing.name)
private readonly AuditingModel: Model<Auditing>,
) {}
async intercept(
context: ExecutionContext,
next: CallHandler,
): Promise<Observable<any>> {
const request = context.switchToHttp().getRequest();
const response = context.switchToHttp().getResponse();
const { headers, method, ip, route, query, body } = request;
let bodyParam = Object.assign({}, body),
files: any;
const newReq = cloneDeep(request); // lodash cloneDeep
const newRes = cloneDeep(response);
const postMulterRequest: any = await new Promise((resolve, reject) => {
const multerReponse = multer().any();
multerReponse(newReq, newRes, (err) => {
if (err) reject(err);
resolve(newReq);
});
});
files = postMulterRequest?.files;
return next.handle().pipe(
tap(() =>
this.AuditingModel.create({
request: {
query,
bodyParam,
files,
},
timeAccessed: new Date().toISOString(),
}),
),
);
}
}
Summary of what I need to do here is I need to intercept and log the file in our DB before it gets processed in the method/endpoint that uses #UseInterceptors(FileInterceptor('file')).
I have solve this by intercepting the request using the
#Req() req
and creating a method to handle the files that was intercepted inside the FileInterceptor decorator.
Code Example:
// create logs service first to handle your queries
createLogs(file, req){
// do what you need to do with the file, and req here
const { filename } = file;
const { ip } = req
....
}
// main service
// inject the service first
constructor(#Inject(LogsService) private logsService: LogsService)
uploadHandler(file, req){
this.logsService.createLogs(file, req)
// proceed with the next steps
....
}
// controller
#Post('upload')
#UseInterceptors(FileInterceptor('file'))
testFunction(#UploadedFile() file: Express.Multer.File,, #Req req){
return this.serviceNameHere.uploadHandler(file, req);
}

How to pass a child Interface to a parent class?

I have this:
LocationController.ts
import {GenericController} from './_genericController';
interface Response {
id : number,
code: string,
name: string,
type: string,
long: number,
lat: number
}
const fields = ['code','name','type','long','lat'];
class LocationController extends GenericController{
tableName:string = 'location';
fields:Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
}
export {get};
GenericController.ts
interface Response {
id : number
}
export class GenericController{
tableName:string = '';
fields:Array<any> = [];
_get = async (req, res) => {
try{
const id = req.body['id'];
const send = async () => {
const resp : Array<Response> = await db(this.tableName).select(this.fields).where('id', id)
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
}
await send();
}catch (error){
// some error handling
}
}
}
What I want to do is to pass the Response interface from LocationController to the GenericController parent, so that the response is typed accurately depending on how the child class has defined it. Clearly it doesn't work like this since the interface is defined outside of the class so the parent has no idea about the Response interface in the LocationController.ts file.
I've tried passing interface as an argument in the constructor, that doesn't work. So is there a way I can make this happen? I feel like I'm missing something really simple.
Typically, generics are used in a situation like this. Here's how I'd do it:
interface Response {
id: number;
}
// Note the generic parameter <R extends Response>
export class GenericController<R extends Response> {
tableName: string = "";
fields: Array<any> = [];
_get = async (req, res) => {
try {
const id = req.body["id"];
const send = async () => {
// The array is now properly typed. You don't know the exact type,
// but you do know the constraint - R is some type of `Response`
let resp: Array<R> = await db(this.tableName).select(this.fields).where("id", id);
if (resp[0] === undefined) {
// some error handling
}
res.status(status.success).json(resp[0]);
};
await send();
} catch (error) {
// some error handling
}
};
}
import { GenericController } from "./_genericController";
interface Response {
id: number;
code: string;
name: string;
type: string;
long: number;
lat: number;
}
const fields = ["code", "name", "type", "long", "lat"];
// Here we tell the GenericController exactly what type of Response it's going to get
class LocationController extends GenericController<Response> {
tableName: string = "location";
fields: Array<any> = fields;
}
const locationController = new LocationController();
const get = async (req, res) => {
await locationController._get(req, res);
};
export { get };
If this is not enough and you wish to somehow know the exact response type you're going to get, I believe the only way is a manual check. For example:
import { LocationResponse } from './locationController';
// ... stuff
// Manual runtime type check
if (this.tableName === 'location') {
// Manual cast
resp = resp as Array<LocationResponse>
}
// ...
You could also check the form of resp[0] (if (resp[0].hasOwnProperty('code')) { ... }) and cast accordingly. There are also nicer ways to write this, but the basic idea remains the same.
Generally, a properly written class should be unaware of any classes that inherit from it. Putting child-class-specific logic into your generic controller is a code smell. Though as always, it all depends on a particular situation.

I need suggestion in saving logs in NestJS

I need suggestion about how to properly or most efficient way to save user logs in database.
So I want to log every time the user do CRUD. I want to save the
userId
event or the action, if he did he remove or update
the Json response of the api
and also the request response of the api.
what I have, in user service layer I call another service name auditService which insert the logs in database.
async create(createUserDto: CreateUserDto): Promise<User> {
try {
const user = new User();
user.email = createUserDto.email;
user.role = createUserDto.role;
user.firstName = createUserDto.firstName;
user.lastName = createUserDto.lastName;
const rs = await this.usersRepository.save(user);
const audit = new AuditLog();
audit.userId = rs.id;
audit.eventType = CREATE_CUSTOMER_SUCESS;
audit.rqMessage = createUserDto;
audit.rsMessage = rs;
//Audit Service which save the logs.
await this.auditService.create(audit);
return rs;
} catch (err) {
// Error
}
Well, this works. but I know there is more efficient way than this. Thank you.
To have full access over request and Response, the best way is by setting a Logger Interceptor or Middleware.
For example, if you are keeping the log to MongoDB, here is an example:
#Injectable()
export class LoggingInterceptor implements NestInterceptor {
constructor(#InjectModel('Log') private logModel: Model<LogDocument>) {}
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context?.switchToHttp()?.getRequest<Request>();
const { statusCode } = context?.switchToHttp()?.getResponse<Response>();
const { originalUrl, method, params, query, body, headers, user } = req;
const requestTime = new Date();
const request: RequestInterface = {
originalUrl,
method,
params,
query,
body,
headers,
};
return next.handle().pipe(
tap((data) => {
const response = { statusCode, data };
this.insertMongo(originalUrl, request, response, requestTime);
}),
);
}
private async insertMongo(
endpoint: string,
request: RequestInterface,
response: ResponseInterface,
requestTime: Date,
): Promise<LogDocument> {
const logInfo: CreateLogDto = {
endpoint,
request,
response,
requestTime,
};
const createdLog = new this.logModel(logInfo);
return createdLog.save();
}
}
It will handle the Request, Response, Context, and Timestamp of every request intercepted.
To use it in a module, you just have to add an APP_INTERCEPTOR provider to it. In the case of the example logger, it should look like this:
providers: [
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
],

Resources