Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to intercept field resolvers? #352

Closed
saward opened this issue Sep 25, 2018 · 5 comments
Closed

How to intercept field resolvers? #352

saward opened this issue Sep 25, 2018 · 5 comments

Comments

@saward
Copy link

saward commented Sep 25, 2018

Hi,

I am trying to work out how to inject authorisation functions into my project. Consider, for example, a contrived example involving a Client object:

type Client struct {
	ID                string       `json:"id"`
	Dob               string       `json:"dob"`
	Created           time.Time
	UserID            string
}

And then a resolver to fetch it (as well as resolvers for Created and User):

func (r *queryResolver) Client(ctx context.Context, id string) (*models.Client, error) {
	client, err := loaders.Loader.GetClient(ctx, id)
	return &client, err
}

And a query:

{
  client(id: "0378d3af-707f-4b84-b696-e334aaf8ebf4"){
    id
    dob
    created
  }
}

Suppose that I want to disallow the viewing of the 'dob' field for anyone except the user that owns this client object.

Right now, I can create a ResolverMiddleware that intercepts before a response is sent. I could in that, I assume, do some work to check the relevant values and continue on or reject depending on authorisation policy.

However, the difficulty is that I think I need to fetch the client object twice -- once in the Client() resolver, and once in my custom authorisation function. Looking at the generated code, it seems that the 'client' object that's fetched as part of the 'Client()' resolver function isn't passed to the ResolverMiddleware in any way. This means that in order to get, say, the UserID value to help determine if I'll allow the Dob string to be returned, I need to fetch the client object again (or do some separate caching of the result -- such as https://gqlgen.com/reference/dataloaders/).

I wondered if:

a) I'm reading the code right, and I have understood the above correct
b) There's a better way to have access to the data I need to perform the authorisation calculations in question

I could of course create resolvers for every field, since those get passed the Client object, but that seems like unnecessary boilerplate code.

@mathewbyrne
Copy link
Contributor

mathewbyrne commented Sep 25, 2018

A ResolverMiddleware is part of a middleware stack, so it doesn't get passed the value on invocation, instead you need to call next and inspect the value it returns.

func ExampleMiddleware(ctx context.Context, next Resolver) (res interface{}, err error) {
    res = next(ctx);
    // Inspect res and alter if required.
    return res, nil
}

We recommend injecting authentication credentials through context.

There's a better way to have access to the data I need to perform the authorisation calculations in question

There is, we support field-level directives (docs), which are FieldMiddleware that you can specify where they run in your schema. This might be a better fit for your use-case, since you can just have some authorization directive on the field itself.

Here's an example of what this might look like:

type Client {
	id String!
	dob String @adminOnly
}

You will then find that you have an AdminOnly middleware generated for you on the DirectiveRoot that you can implement.

@saward
Copy link
Author

saward commented Sep 25, 2018

Thanks for the reply. I had been aware of directives, but I'm trying to use OPA (https://www.openpolicyagent.org/) for handling authorisation, and the policies will be more complicated than I think is feasible with directives. I'd also prefer not to have policy data like that contained within the schema.

I hadn't fully realised that the value was returned as part of 'res', and that's helped a lot with finding a solution for this. Thanks!

@saward saward closed this as completed Sep 25, 2018
@mathewbyrne
Copy link
Contributor

You make a good point though about resolver middleware's not having access to the parent object, especially since directive middlewares do have access. I'll haver a think about whether we want to add it to the resolver middleware interface.

@saward
Copy link
Author

saward commented Sep 25, 2018

That sounds good, thanks!

@stale
Copy link

stale bot commented Aug 28, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale label Aug 28, 2019
@stale stale bot closed this as completed Sep 4, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants