[ ] Regression
[ ] Bug report
[x] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.
Pipes are running in sequence 'globalMetadata', 'classMetadata', 'methodMetadata'.
Pipes are running in reverse sequence from concrete to common - 'methodMetadata', 'classMetadata', 'globalMetadata'.
For example, you have some global pipe, which transform request params to database entities
@Get(':id')
async getOneAction(@RequestEntity() bot: Bot): Promise<Bot> {
return bot;
}
RequestEntity decorator returns some data for global pipe. Global pipe with injected database service returns entity. But now for certain actions you might have to set additional parameters to database query through another pipe:
@Get(':id')
async getOneAction(@RequestEntity({}, new OnlyActiveEntityPipe()) bot: Bot): Promise<Bot> {
return bot;
}
but right now pipe OnlyActiveEntityPipe can be executed only after global pipe
Nest version: X.Y.Z
For Tooling issues:
- Node version: XX
- Platform:
Others:
Looks like right now there is no way to change priority for pipes (interceptors): they are running in sequence 'globalMetadata', 'classMetadata', 'methodMetatada'.
I think, feature for setting priority for certain pipes might be useful.
Also, the logic when pipes are running from concrete to common appears more straightforward (and more useful)
Hi @anyx,
Thanks for sharing your thoughts.
Also, the logic when pipes are running from concrete to common appears more straightforward (and more useful)
All features, including pipes, interceptors, guards, and exception filters are running from common to concrete due to keeping a consistency between them, making them cohesive, and intuitive when you think about the incoming request as a pipeline. Think about interceptors, they follow a simple request processing pipeline, being executed from global to concrete once the request wants to hit an end-handler, and then (in response pipeline), they are executed from concrete to global (if you attach some asynchronous/mapping logic inside them and so on). It looks the same with guards. You could have a global authorization guard, then (tied at class-level) roles guard, and afterward (tied at method-level) permission guard that checks a set of user permissions to validate whether the request should be handled or not. Let's think about pipes - same here we should start with simple DTO validation (using, let's say built-in ValidationPipe
), then, at class-level, we could have UserByIdPipe
that selects user by ID passed in DTO.
RequestEntity decorator returns some data for global pipe. Global pipe with injected database service returns entity. But now for certain actions you might have to set additional parameters to database query through another pipe:
In your case, you should rather use interceptor that makes use of Reflector
. Through @ReflectMetadata()
decorator, you should define a metadata to the particular handler which then you could reflect inside the interceptor, and based on picked value - run an appropriate database query. Afterward, attach the result to the request
object and pull out this value using custom decorators feature. 馃檪
@kamilmysliwiec I have an interceptor applied at the controller level that is doing a db lookup and attaching an entity to request.team if an id parameter is provided. I also have a custom decorator to extract the value in the controller method.
Now i am trying to implement a guard, at the method level, that would deny access if request.team.ownerId !== request.user.id but the guard is always executed before the interceptor. So request.team is always undefined.
Do you have any suggestions on this use case?
Edit: Looks like the passport module is attaching request.user using a guard. So would the suggestion be to use a guard and not an interceptor then use a custom deocrator for say IsOwner(true)
https://github.com/nestjs/passport/blob/master/lib/auth.guard.ts#L48
Edit: my edit above works so i am rolling with that. Still would like to know if that is the best practice. I am no longer using an interceptor and my guard looks like this
@Injectable()
export class TeamGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
private readonly teamsService: TeamsService,
) {}
async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest();
if (request.params.id) {
request.team = await this.teamsService.findOne({ id: request.params.id });
}
const owner = this.reflector.get<string[]>('owner', context.getHandler());
if (owner && request.team.ownerId !== request.user.id) {
return false;
}
return true;
}
}
then i am using guard on the controller and i have a custom decorater for Owner(true).
This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.
Most helpful comment
@kamilmysliwiec I have an interceptor applied at the controller level that is doing a db lookup and attaching an entity to request.team if an id parameter is provided. I also have a custom decorator to extract the value in the controller method.
Now i am trying to implement a guard, at the method level, that would deny access if request.team.ownerId !== request.user.id but the guard is always executed before the interceptor. So request.team is always undefined.
Do you have any suggestions on this use case?
Edit: Looks like the passport module is attaching request.user using a guard. So would the suggestion be to use a guard and not an interceptor then use a custom deocrator for say IsOwner(true)
https://github.com/nestjs/passport/blob/master/lib/auth.guard.ts#L48
Edit: my edit above works so i am rolling with that. Still would like to know if that is the best practice. I am no longer using an interceptor and my guard looks like this
then i am using guard on the controller and i have a custom decorater for Owner(true).