Hotchocolate: Code Organization and Authorization Question

Created on 26 Oct 2019  ยท  5Comments  ยท  Source: ChilliCream/hotchocolate

Describe the bug
First question:
I'm using multiple querytype and query classes. For example: UserQuery, UserqueryType. And than I have ONE AppqueryType class where I combine all , this file will than be added via ".AddQueryType()" in the startup file.
My AppQuerType file looks like this:

public class AppQueryType : ObjectType
    {
        protected override void Configure(IObjectTypeDescriptor descriptor)
        {
            base.Configure(descriptor);

            descriptor.Include<UserQuery>();
            descriptor.Include<HomeQuery>();
        }
    }

And it works like expected so I think that is a correct way of doing it. Aand I haven't found anything to that topic online.
Is that the correct way to include multiple queryfiles into one for cleaner/organized code???

Second question:
I wanted to implement authorization and if I understand it correctly I only need to add "discriptor.authorize()" to put that authorize validation to all Fields or if I want it on only one I will add the ".authorize()" to that specific field on the Query/MutationType class.
I tested it on my AppqueryType class and
it works somehow:

protected override void Configure(IObjectTypeDescriptor descriptor)
        {
            base.Configure(descriptor);

            descriptor.Include<UserQuery>().Authorize(new []{CustomRoles.Admin});
            descriptor.Include<HomeQuery>().Authorize("Test_2");
        }
    }

Even if I only put one authorize tag to one discriptor(Line) ALL queries will have that authorization.
In this case only the "Test_2" is getting triggerd for both fields. So my conclusion was -> maybe the appqueryType class is wrong, therefore question one.

If I implement it on any Querytype class for example on the UserQueryType class:

public class UserQueryType : ObjectType<UserQuery>
    {
        protected override void Configure(IObjectTypeDescriptor<UserQuery> descriptor)
        {
            base.Configure(descriptor);

            descriptor.Field(x => x.GetUsers(default))
                .Authorize(new []{CustomRoles.Admin})
                 // or .Authorize("Admin") // or .Authorize(new[]{"Admin")
                .Type<ListType<UserType>>();

            descriptor.Field(x => x.GetUser(default, default))
                .Authorize("Test_2")
                .Argument("id", x => x.Type<NonNullType<IdType>>());
        }
    }

Than no authorization is triggered at all. I created policies and roles so that my user should only get "not authorized" errors.
Am i doing something wrong here?

โ“ question

Most helpful comment

Thanks for the great response to question one.
Maybe consider adding a small text to the documentation where you say/show how you can include multiple types into one main type class, for a cleaner/organized appquerytype.
For me at least it was not clear by reading the documentation.

Do you have any solution/thoughts to my second question?

All 5 comments

Hi,

it is one way you can do it.

  1. way including resolver types into a type:
public class AppQueryType : ObjectType
{
    protected override void Configure(IObjectTypeDescriptor descriptor)
    {
        base.Configure(descriptor);

        descriptor.Include<UserQuery>();
        descriptor.Include<HomeQuery>();
    }
}

While this is simple and works, you are importing types here and they are inferred. So, you do not have fill control about these fields.

Also the included types and the actual types are strongly coupled.

  1. Selective inclusion of fields.
public class AppQueryType : ObjectType
{
    protected override void Configure(IObjectTypeDescriptor descriptor)
    {
        base.Configure(descriptor);

        descriptor.Field<UserQuery>(t => t.Foo).Type<StringType>();
        descriptor.Field<HomeQuery>(t => t.Foo).Type<StringType>();
    }
}

You can also include fields selectively which gives you more control but still leaves the types strongly coupled.

  1. Type Extensions
public class PersonTypeExtension
    : ObjectTypeExtension
{
    protected override Configure(IObjectTypeDescriptor descriptor)
    {
        descriptor.Name("Person");
        descriptor.Field("address")
            .Type<NonNullType<StringType>>()
            .Resolver(/"Resolver Logic"/);
        descriptor.Field<UserQuery>(t => t.Foo).Type<StringType>();
    }
}

https://hotchocolate.io/docs/schema-object-type#extension

Type extensions can be added to the schema builder like any other type. When they are added its fields will be merged with a matching type.

With this you are loosely coupled. We will add more features to this in the coming versions.

Whatever way you choose depends on your needs and taste.

Thanks for the great response to question one.
Maybe consider adding a small text to the documentation where you say/show how you can include multiple types into one main type class, for a cleaner/organized appquerytype.
For me at least it was not clear by reading the documentation.

Do you have any solution/thoughts to my second question?

After testing out the hole day I can't figure out how to "solve" my problem.
And I think I explained question one a bit wrong.
I want to have one AppQuerytype class that includes all the other type classes, the type classes look, like in every example :

public class UserMutationType : ObjectType<UserMutation>
    {
        protected override void Configure(IObjectTypeDescriptor<UserMutation> descriptor)
        {
            base.Configure(descriptor);
            descriptor.Field<UserMutation>(x => x.RegisterUserAsync(default))
             .Type<stringType> ;

            descriptor.Field<UserMutation>(x => x.DeleteUserAsync(default))
                .Authorize("HomeUserPolicy").Type<stringType>; 
        }
    }

that is just an example class, for one mutation class, I generated quickly. In "UserMutation" are all the called functions for users(registerUserAsync , etc)
Now i want to have multiple of these Typeclasses(for home, groups, device, etc) included in One class -> in this case AppQueryType class. And that class is added in the "startup".
Or do I need to type all of these functions" descriptor.Field(x => x.LoginUserAsync(default))" in one query/mutation/subscription class, that makes after like 10 of these harder to read and not so clean.

It works how I did it before, but when you want to add authorization to 7 out of 8 Fields it is not possible without writing all Fields.
And not just say
descriptor.Include<UserQuery>().Authorize("HomeUserPolicy"); desciptor.Include<HomeQuery>();
that first line will add authorization to the whole type

type Queries @authorize(policy: "HomeUserPolicy", roles: [  ]) {
  home(guid: String): Home
  homes: [Home]
  user(guid: String): User
  users: [User]
}

not just to the userqueries.

Is that possible?
I'm not sure if I explained it understandably.

In your case you have to use extensions because you cannot merge multiple types..

Include, only includes resolvers from which fields can be inferred.

In your case I would go with the type extensions.

I will give you an answer on your second part tomorrow.

The included fields can now be correctly changed in the including type.

Was this page helpful?
0 / 5 - 0 ratings