CONTEXT
I am looking for a way to force the programmer to validate the authorization of each parameter of a request route.
The objective is to avoid possible security errors due to forgetting to validate the authorization of a parameter.
Some of you could say that this belongs more to the development cycle than to the framework itself, that this should be solved by specifying who is authorized to access a resource in the task definition, and that the tests would verify that this authorization is carried out correctly, but could be forgotten. So some of you will think that this would be solved in the subsequent code review, but it could be forgotten. So some of you will think well that would be solved in the functional test, anyway it could be forgotten.
My goal is to make sure that the programmer cannot forget that he must be aware of the authorization of the resource, even if he has to actively ignore the authorization for resources that are public.
How do I intend to carry it out?
I thought the best way would be to set an interceptor just before entering the controller that checks that all parameters have been changed to an object like {nParameter: {value: nValue, validate: true}}. And in this way force the programmer to establish another interceptor before this one that does that mapping by setting the parameter as validated or not depending on the case.
The problem is that I don't see a way to set that interceptor globally and have it run last since having to enter it on every resource would take us back to where we started.
I want to think that with some observable magic it could be achieved, I have done this:
#Injectable()
export class SecurityInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest<Request>()
request.params = Object.entries(request.params)
.reduce((params, [key, value]) => {
params[key] = { value, validate: false }
return parameters
}, {} as any)
return next.handle().pipe(
map((response) => {
if (Object.values(request.params).some(({ validate }) => !validate)) {
throw new Error()
}
return response
}),
)
}
}
But I would like to prevent the controller from running if not all parameters are checked.
Related
I am pretty new to this kind of stuff so any advice is appreciated, especially if there's a better way to solve this than what I am looking for.
I am trying to support an old endpoint by redirecting it to a new endpoint, and both endpoints should support query parameters. Each endpoint lives in a separate controller.
Example:
I want /old-namespace/getStuff?foo=bar to redirect to /new-namespace/getStuff?foo=bar without manually rebuilding a query string like the Nest docs point out, because the params can be dynamic.
Looking at the NestJS docs, there is a handy #Redirect decorator that you can use on an endpoint like so, to easily redirect the request to a different URL:
#Get('docs')
#Redirect('https://docs.nestjs.com', 302)
getDocs(#Query('version') version) {
if (version && version === '5') {
return { url: 'https://docs.nestjs.com/v5/' };
}
}
However, when using this, request.query is cleared on redirect (pretty sure this is expected behavior). Does anyone have a minimally-invasive solution for this? I've tested out building middleware/interceptors/custom decorators to get around this with varying degrees of success, but that seems heavy-handed and I wish there was a way to throw an extra param in the #Redirect decorator like retainQuery = true or something.
I ended up using the top answer here: How to rewrite url path in Nestjs?
Ambroise Rabier's first response on nginx rewrites cued me in to search for a simple middleware solution since I didn't want to inject a service in multiple places, as there will be quite a few small redirects from old namespaces to new ones.
In a common module:
consumer
.apply(RewriteApiEndpointMiddleware)
.forRoutes('/')
In a middleware:
import { Injectable, NestMiddleware } from '#nestjs/common';
#Injectable()
export class RewriteApiEndpointMiddleware implements NestMiddleware {
use(req: any, res: any, next: () => void) {
// the linked answer replaced "api" but in my case I needed to send "app" requests somewhere else
req.url = req.url.replace(/^\/app/, '/new-namespace');
next();
}
}
What you need is called rewrite in nginx.
I don't know if it work, but maybe you can do that ?
#Get('/old-namespace/getStuff')
getStuff(#Query() query) {
// Done !
getNewStuff(query);
}
#Get('/new-namespace/getStuff')
getNewStuff(#Query() query) {
}
If not, you can always do something like:
function handleGetNewStuff() {
}
#Get('/old-namespace/getStuff')
getStuff(#Query() query) {
handleGetNewStuff(query);
}
#Get('/new-namespace/getStuff')
getNewStuff(#Query() query) {
handleGetNewStuff(query);
}
However, if your issue is about changing the link on the user side (and not just for internal routing), you can try (not tested either):
#Get('/old-namespace/getStuff')
getStuff(#Req() request, #Res() response) {
const newURL = req.originalUrl.replace('old', 'new');
response.redirect(newURL);
}
Remember that NestJS use expressJS bellow.
Using MikroORM and getting this error:
ValidationError: Using global EntityManager instance methods for context specific actions is disallowed.
If you need to work with the global instance's identity map, use `allowGlobalContext` configuration option or `fork()` instead
The code that it corresponds to is below:
import { MikroORM } from "#mikro-orm/core";
import { __prod__ } from "./constants";
import { Post } from "./entities/Post";
import mikroConfig from "./mikro-orm.config";
const main = async () => {
const orm = await MikroORM.init(mikroConfig);
const post = orm.em.create(Post, {
title: "my first post",
});
await orm.em.persistAndFlush(post);
await orm.em.nativeInsert(Post, { title: "my first post 2" });
};
main().catch((error) => {
console.error(error);
});
I am unsure where I need to use the .fork() method
Don't disable validations without understanding them!
I can't believe what I see in the replies here. For anybody coming here, please don't disable the validation (either via MIKRO_ORM_ALLOW_GLOBAL_CONTEXT env var or via allowGlobalContext configuration). Disabling the validation is fine only under very specific circumstances, mainly in unit tests.
In case you don't know me, I am the one behind MikroORM, as well as the one who added this validation - for a very good reason, so please don't just disable that, it means you have a problem to solve, not that you should add one line to your configuration to shut it up.
This validation was added to MikroORM v5 (so not typeorm, please dont confuse those two), and it means exactly what it says - you are trying to work with the global context, while you should be working with request specific one. Consult the docs for why you need request context here: https://mikro-orm.io/docs/identity-map#why-is-request-context-needed. In general using single (global) context will result in instable API response and basically a one huge memory leak.
So now we should understand why the validation is there and why we should not disable it. Next how to get around it properly.
As others mentined (and as the validation error message mentioned too), we can create fork and use that instead:
const fork = orm.em.fork();
const res = await fork.find(...);
But that would be quite tedious, in real world apps, we usually have middlewares we can use to do this for us automatically. That is where the RequestContext helper comes into play. It uses the AsyncLocalStorage under the hood and is natively supported in the ORM.
Following text is mostly an extraction of the MikroORM docs.
How does RequestContext helper work?
Internally all EntityManager methods that work with the Identity Map (e.g. em.find() or em.getReference()) first call em.getContext() to access the contextual fork. This method will first check if we are running inside RequestContext handler and prefer the EntityManager fork from it.
// we call em.find() on the global EM instance
const res = await orm.em.find(Book, {});
// but under the hood this resolves to
const res = await orm.em.getContext().find(Book, {});
// which then resolves to
const res = await RequestContext.getEntityManager().find(Book, {});
The RequestContext.getEntityManager() method then checks AsyncLocalStorage static instance we use for creating new EM forks in the RequestContext.create() method.
The AsyncLocalStorage class from Node.js core is the magician here. It allows us to track the context throughout the async calls. It allows us to decouple the EntityManager fork creation (usually in a middleware as shown in previous section) from its usage through the global EntityManager instance.
Using RequestContext helper via middleware
If we use dependency injection container like inversify or the one in nestjs framework, it can be hard to achieve this, because we usually want to access our repositories via DI container, but it will always provide we with the same instance, rather than new one for each request.
To solve this, we can use RequestContext helper, that will use node's AsyncLocalStorage in the background to isolate the request context. MikroORM will always use request specific (forked) entity manager if available, so all we need to do is to create new request context preferably as a middleware:
app.use((req, res, next) => {
RequestContext.create(orm.em, next);
});
We should register this middleware as the last one just before request handlers and before any of our custom middleware that is using the ORM. There might be issues when we register it before request processing middleware like queryParser or bodyParser, so definitely register the context after them.
Later on we can then access the request scoped EntityManager via RequestContext.getEntityManager(). This method is used under the hood automatically, so we should not need it.
RequestContext.getEntityManager() will return undefined if the context was not started yet.
Simple usage without the helper
Now your example code from the OP is very basic, for that forking seems like the easiest thing to do, as its very bare bones, you dont have any web server there, so no middlewares:
const orm = await MikroORM.init(mikroConfig);
const emFork = orm.em.fork(); // <-- create the fork
const post = emFork.create(Post, { // <-- use the fork instead of global `orm.em`
title: "my first post",
});
await emFork.persistAndFlush(post); // <-- use the fork instead of global
await orm.em.nativeInsert(Post, { title: "my first post 2" }); // <-- this line could work with the global EM too, why? because `nativeInsert` is not touching the identity map = the context
But we can use the RequestContext here too, to demonstrate how it works:
const orm = await MikroORM.init(mikroConfig);
// run things in the `RequestContext` handler
await RequestContext.createAsync(orm.em, async () => {
// inside this handler the `orm.em` will actually use the contextual fork, created via `RequestContext.createAsync()`
const post = orm.em.create(Post, {
title: "my first post",
});
await orm.em.persistAndFlush(post);
await orm.em.nativeInsert(Post, { title: "my first post 2" });
});
The #UseRequestContext() decorator
Middlewares are executed only for regular HTTP request handlers, what if we need
a request scoped method outside that? One example of that is queue handlers or
scheduled tasks (e.g. CRON jobs).
We can use the #UseRequestContext() decorator. It requires us to first inject the
MikroORM instance to current context, it will be then used to create the context
for us. Under the hood, the decorator will register new request context for our
method and execute it inside the context.
This decorator will wrap the underlying method in RequestContext.createAsync() call. Every call to such method will create new context (new EntityManager fork) which will be used inside.
#UseRequestContext() should be used only on the top level methods. It should not be nested - a method decorated with it should not call another method that is also decorated with it.
#Injectable()
export class MyService {
constructor(private readonly orm: MikroORM) { }
#UseRequestContext()
async doSomething() {
// this will be executed in a separate context
}
}
Alternatively we can provide a callback that will return the MikroORM instance.
import { DI } from '..';
export class MyService {
#UseRequestContext(() => DI.orm)
async doSomething() {
// this will be executed in a separate context
}
}
Note that this is not a universal workaround, you should not blindly put the decorator everywhere - its actually the opposite, it should be used only for a very specific use case like CRON jobs, in other contexts where you can use middlewares this is not needed at all.
I faced a similar issue today when I upgraded the mikrorm setup from v4 to v5. After doing some RnD, I found the following changes helped me solve the mentioned error.
In the config object which is passed to the MikroORM.init call, pass the following property
allowGlobalContext: true
Don't directly use em to create database entry. Instead use the following code
const post = orm.em.fork({}).create(Post, {
title: "my first post",
});
The above changes should help you fix the error.
I am also very new to MikroORM. so, I am not sure why this error appears. But my uneducated guess is, they are restricting access to any changes to the global EntityManager em instance.
After doing some digging I found this solution:
yarn install dotenv
create a .env file in the root of the project
In your .env file paste the following:
MIKRO_ORM_ALLOW_GLOBAL_CONTEXT = true
Problem solved!
I am trying to build a decorator to "log" request info
export const Tracking = () => {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
const method = descriptor.value;
descriptor.value = async function(...args: any[]) {
console.log(/** Request info */)
console.log(/** Headers, Body, Method, URL...*/)
return method.call(this, ...args);
}
}
}
and try to use it on a controller method like this.
export class Controller {
#Get('/path')
#Tracking()
public async getData(#Headers('user') user: User) {
return this.service.getData(user.id);
}
}
If this is impossible, is there a way to apply interceptor to some method of controller?
Or is there a thread(like)-level context for request?
Thanks!!
Decorators don't have access to the request information because of what a decorator is. It's a higher order function that is called to set metadata for the class, class member, class method, or class method parameter. This metadata can be read at runtime, but it is called and set essentially as soon the file is imported. Due to this, there's no way to call a decorator on each request, even Nest's #Body() and #Req() are called at the time of import and read at the time of the request (actually earlier but that's besides the point).
What you're looking for here sounds more like an interceptor, like Micael Levi and hoangdv have already mentioned. The Nest docs show a basic logging example, and there are packages out there like #ogma/nestjs-module (disclaimer: I'm the author) that handle this request logging/tracking for you including the addition of correlation IDs.
I want to find a table row by request parameter. I know how to do it in service but I'm trying
to do it also in decorator.
My decorator:
import { BadRequestException, createParamDecorator, ExecutionContext } from '#nestjs/common';
export const GetEvent = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
const { eventId } = request.params;
// Something like in service:
// const event = await this.eventModel.findByPk(eventId);
// return event;
});
I know that it's impossible to inject service in decorator but maybe some hacks to make database requests before calling service methods?
Theoretically, you could use a pacakge directly (like if you use TypeORM you could use the typeorm package), but there's a few things to note:
decorators really shouldn't be calling databases, it'll lead to crazy difficult stack traces
decorator methods can't be async, so you'd need callbacks, which decorators don't really work well with
querying the database really should be done in the controller or service. Just a best practice.
I am trying to access to Request object from within a Validation Pipe in nestjs
In order to verify uniqueness of certain fields, I require the ID/UUID parameters supplied with PUT/PATCH request (not available in the data structure itself)
any idea?
Currently, it is not possible to access the request object at all in a pipe. If you need the request you can use a guard or an interceptor.
If you are working on verifying uniqueness, that sounds like a part of business logic more than just about anything else, so I would put it in a service and handle the query to the database there. Just my two cents.
Edit 11/17/2020
After learning way more about how the framework works as a whole, technically it is possible to get the entire request object in a pipe. There are two ways to go about it.
Make the pipe #Injectable({ scope: Scope.REQUEST }) so that it is request scoped. This will end up creating a new pipe on each request, but hey, if that's your cup of tea then great.
Make a custom parameter decorator as custom decorators get completely passed into pipes as they are. Do note, that this could impact how the ValidationPipe is functioning if that is bound globally, at the class, or method level.
We can create a Pipe and access request object. We can move further and update the Body as well, if needed.
Following is an example scenario, where createdBy field should be added to the Body dynamically. Let's say user details are available from request:
// user.pipe.ts
import { Request } from 'express'
import { REQUEST } from '#nestjs/core'
import { Injectable, Inject, Scope, PipeTransform } from '#nestjs/common'
#Injectable({ scope: Scope.REQUEST })
export class UserPipe implements PipeTransform<any> {
constructor(#Inject(REQUEST) protected readonly request: Request) {}
transform(value) {
let email = this.request["user"].email;
value['createdBy'] = email;
return value
}
}
We can now use this in controller like this:
// someentity.controller.ts
#Post()
public async create(
#Body(SetUserPipe) dto: SomeEntityDto,
): Promise<SomeEntity> {
....
}