typegraphql-prisma icon indicating copy to clipboard operation
typegraphql-prisma copied to clipboard

add support for overriding _all decorators on CRUD resolvers

Open hirotachi opened this issue 4 years ago • 6 comments

Is your feature request related to a problem? Please describe.

When applying decorators on all the methods of a model CRUD resolver, there is no way to remove or override those decorators on specific resolvers e.g:

applyResolversEnhanceMap({
  Story: {
    _all: [Authorized(Role.ADMIN, Role.DEVELOPER)],
    stories: [Authorized()],
    story: [Authorized()]
  },
})

the _all decorators are applied to all Story resolvers, if you want to solve this you have to remove the _all field and write all the CRUD resolvers that you want to apply decorators to.

Describe the solution you'd like

An option could be introduced to accept both an array of decorators and a function for all CRUD resolvers except for the _all field, this function can be passed in with the decorators passed in the _all field, and it can return more decorators or none at all:

applyResolversEnhanceMap({
  Story: {
   _all: [Authorized(Role.ADMIN, Role.DEVELOPER)],
   stories:(decorators) => [Authorized()],
   story: (decorators) => [],
   createStory: (decorators) => [...decorators, RateLimit({ window: "15min", max: 10 })]
  },
})

there is also the option to just pass in an array of decorators like the current implementation and it will just add the decorators passed in the _all field e.g:

applyResolversEnhanceMap({
  Story: {
    _all: [Authorized(Role.ADMIN, Role.DEVELOPER)],
    stories:(decorators) => [Authorized()],
    createStory: [RateLimit({ window: "15min", max: 10 })],
  },
})

hirotachi avatar Jul 14 '21 20:07 hirotachi

This would be a very helpful feature - what needs to happen for it to be implemented?

oohwooh avatar Oct 01 '21 01:10 oohwooh

Agreed, would be quite helpful since I close all on default but require finer checks for certain actions:

applyResolverEnhanceMap({
  Story: {
    _all: [Authorized()],
    createStory: [Authorized(Role.ADMIN]
  },
})

Gr3yShad0w avatar Oct 15 '21 12:10 Gr3yShad0w

@Gr3yShad0w Even raw TypeGraphQL does not support a resolver-level decorators like @Authorized().

I think that it would be much simpler if you define a decorator on resolver and then on the field, so the middleware can read extensions and use default value or per action value.

That would solve the issue with overriding default decorator value as _all is just a shorthand, workaround that was not supposed to handle such cases.

MichalLytek avatar Oct 15 '21 12:10 MichalLytek

I believe the issue here is to be able to define a default decorator for all query/mutation in a resolver class via _all as well as override the said default decorator (or add more) for specific queries/mutations.

This is so that we can prevent having to mention all the generated queries/mutations within the map, when we only want to override one (or a few) queries/mutations.

For example:

  • Project model defines project entities that only an ADMIN or MANAGER should be able to create or delete, and…
  • All users should be able to query projects as well as fetch and mutate relations for any project.

In this case, the applyResolverEnhanceMap call tends to become like this:

applyResolverEnhanceMap({
  Project: {
    // All authenticated users can perform these queries/mutation
    aggregateProject: [Authorized()],
    project: [Authorized()],
    projects: [Authorized()],
    findFirstProject: [Authorized()],
    groupByProject: [Authorized()],
    updateProject: [Authorized()],
    
    // Mutations reserved for Admins or Managers
    updateManyProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    createProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    createManyProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    deleteProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    deleteManyProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    upsertProject: [Authorized(Role.ADMIN, Role.MANAGER)],
  },
})

The solution proposed by the OP above could reduce this to the following:

applyResolverEnhanceMap({
  Project: {
    // All authenticated users can perform these queries/mutation
    _all: [Authorized()],

    // These can be removed
    // aggregateProject: [Authorized()],
    // project: [Authorized()],
    // projects: [Authorized()],
    // findFirstProject: [Authorized()],
    // groupByProject: [Authorized()],
    // updateProject: [Authorized()],

    // Mutations reserved for Admins or Managers
    updateManyProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    createProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    createManyProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    deleteProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    deleteManyProject: [Authorized(Role.ADMIN, Role.MANAGER)],
    upsertProject: [Authorized(Role.ADMIN, Role.MANAGER)],
  },
})

As @MichalLytek said, the resolver class itself does not (nor should it, for simplicity's sake) support decorators like @Authorized(). We're only focused on overriding the _all decorator for individual queries/mutations within the resolver class.

jagged3dge avatar Jan 08 '22 06:01 jagged3dge

If I might add a suggestion as well:

I am currently working on a project, using TypeGraphQL-Prisma, where I often encounter the case of 'any authenticated user can Read objects, but only admins can Create, Update, or Delete'.

as such my enhance maps look like this:

const resolversEnhanceMap: ResolversEnhanceMap = {
  Event: {
    event: [Authorized()],
    events: [Authorized()],
    findFirstEvent: [Authorized()],
    aggregateEvent: [Authorized()],
    createEvent: [Authorized<Role>(Role.ADMIN)],
    createManyEvent: [Authorized<Role>(Role.ADMIN)],
    upsertEvent: [Authorized<Role>(Role.ADMIN)],
    updateEvent: [Authorized<Role>(Role.ADMIN)],
    updateManyEvent: [Authorized<Role>(Role.ADMIN)],
    deleteEvent: [Authorized<Role>(Role.ADMIN)],
    deleteManyEvent: [Authorized<Role>(Role.ADMIN)],
  },
};

Might it be benificial to implement, in addition to the _all shorthand, shorthands like _create, _update, _read, and _delete?
It seems like this could already greatly reduce the amount of code needed to specify separate config for queries and mutations that belong together.

something like

const resolversEnhanceMap: ResolversEnhanceMap = {
  Event: {
    _create: [Authorized<Role>(Role.ADMIN)]
    _read: [Authorized()],
    _update: [Authorized<Role>(Role.ADMIN)]
    _delete: [Authorized<Role>(Role.ADMIN)]
  },
};

or even

const resolversEnhanceMap: ResolversEnhanceMap = {
  Event: {
    _mutate: [Authorized<Role>(Role.ADMIN)]
    _query: [Authorized()],
  },
};

This could still work in conjunction with the proposed overrides, if needed.

MitchJans avatar Jun 23 '22 10:06 MitchJans

@MitchJans nice idea, tracked by #300 💪

MichalLytek avatar Jun 23 '22 11:06 MichalLytek

@MitchJans exactly! I copy over the map and reduce it to the enhancemap, as I'm to lazy to update it in the future.

@MichalLytek appreciate it, that you track it!

tzdesign avatar Oct 05 '22 14:10 tzdesign

Resolved by fdb4bba, extended to support also relation resolvers, models, outputs and inputs. Published in https://github.com/MichalLytek/typegraphql-prisma/releases/tag/v0.24.0 🚀

MichalLytek avatar Feb 22 '23 17:02 MichalLytek