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
feat: filtered introspection queries #46
Conversation
A high level comment about the approach, considering that I have little familiarity with the implementation.
I wonder if this approach won't turn out to be more complicated than having a single preExecution hook. Here's the rationale:
So my proposal (again I don't know if it's better or not than your proposed approach, just food for thought) would be, why don't have have a single preExecution hook which tries all directives applied to all the types/fields/whatever of the current operation? |
Right now:
As you can see, the result is an
I think this is the right direction. We need to change this plugin API a bit to let it works. app.register(mercuriusAuth, {
authContext: authContext,
applyPolicy: authPolicy,
namespace: 'autorization-filtering',
authDirective: 'auth'
})
app.register(mercuriusAuth, {
authContext: hasRoleContext,
applyPolicy: hasRolePolicy,
namespace: 'autorization-filtering',
authDirective: 'hasRole'
}) By doing so, we can collect the multiple hooks, into one single execution. I thought to multiple directive registration too: app.register(mercuriusAuth, {
directives: [
{
authContext: authContext,
applyPolicy: authPolicy,
authDirective: 'auth'
},
{
authContext: hasRoleContext,
applyPolicy: hasRolePolicy,
authDirective: 'hasRole'
}
]
}) But in this case, the plugin is losing a lot of flexibility. |
Agreed on the single preExecution hook as well
Would this be an optional parameter with the default behaviour of the filtered introspection being off? I'm keen to keep it as flexible as possible! How do you propose to implement this? In the situation you described, would you be running 2 |
Yes, we can make it turn on/off for sure
The
No, I would leave just one filtering |
So you'd register the hook in the first registration and then updating the existing hook with the new information in the second registration? Just throwing another idea out here! In order to avoid any complications with the plugins needing to talk to each other and exhibiting different registration behaviour depending on the order of registration, I wonder if it would be better to have a Also, one thing before I forget is we should run some benchmarks on this - before and after! |
This was the initial implementation. The issue within this approach is:
All these GraphQL tree traversals may be expensive so we should reduce them at the minimum. I have committed the single hook per namespace execution: now the filtering core is on the Now the
Yeah sure! |
I see, I thought you were talking about registering a hook per directive usage of fields - my mistake! Either way, let's see how your current plan works out though as you make a good point about keeping tree traversals to a minimum!
Nice - excited to see how this turns out! |
PruneSchema, | ||
FilterTypes, | ||
TransformObjectFields | ||
} = require('@graphql-tools/wrap') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
graphql-tools is not a depdency. Could we avoid it? Or just add it as a dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have added it to the package.json
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How feasible would it be to not include this dependency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have checked it and it requires a lot of GraphQL knowledge about the spec and the graphqljs
implementation to manage all the use cases.
I'm monitoring this issue: graphql/graphql-js#113
Or it could be best to include this feature in the official graphql implementation.
At the moment the issue is 5 years old but still active (people want it)
const isObjectPolicy = fieldName === '__typePolicy' | ||
try { | ||
// https://github.com/graphql/graphql-js/blob/main/src/type/definition.ts#L974 | ||
const info = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to create the GraphQLResolveInfo
that contains information about the operation's execution state, including the field name.
I did not cover all the cases, but it will be slightly different from the info
argument received as input by the resolver function.
Any suggestion to recreate it is highly appreciated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Apologies for the slow response, I've been very busy this week - I will have a look this weekend if that's okay!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of functions exported from the graphql
package that might be of interest here:
buildExecutionContext
buildResolveInfo
(requires an execution context)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I did not find a way to build the info object actually.
Would you mind defining a subset info object in the first place?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you anticipate this being problematic for the type definition of the GraphQLResolveInfo
type?
Depending on the data included, I think this is okay for the initial release of this feature if it's not possible - we'd need to:
- Make this clear in the documentation
What was blocking you on this out of interest?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let me make an example:
if you assign the @auth
directive to a type MyObject
and you will set this type as output for two different Query
, the info
object depends on the query that is being executed - the resolver changes.
Instead, during the filtering, the applyPolicy
function is not being executed as a resolver wrapper, but it is executed on the MyObject
type without any other context.
For this reason I was unable to build the same argument
Sharing a couple of thoughts about the user API of this feature to be considered for implementation:
There are minor compared to a working implementation of this feature of course, but I would nonetheless consider them because the feeling with the current public API is that implementation details are leaking into the public API. |
Renamed the option to
This interface can be implemented in a follow-up PR. it should not change the core logic given to the user this lightweight setup Added a lot more test and use cases Todos:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good stuff!! Could you also add the following please?
- Update to the API docs: https://github.com/mercurius-js/auth/blob/main/docs/api/options.md
- An example file as well
Not sure why the CI stop working 🤔
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice work here!
Co-authored-by: Simone Busoli <simone.busoli@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good stuff! LGTM
Need to change the package version when Mercurius 8.10.0 is released as per this comment: #46 (comment)
@Eomm I'll see if I can replicate that CI error |
Able to replicate the CI error locally, my steps:
I just ran the CI on the default branch and it doesn't fail, so I wonder if it's something to do with a recent commit to Mercurius that's not released yet. The only commit I can see in the last 12 hours is this one: mercurius-js/mercurius@7dfc5dc . The only change that might cause an issue is this one, but I'm not 100% - need to test this theory out: mercurius-js/mercurius@7dfc5dc#diff-54f4c0dbde15b4d086d534779d154989affc0010203b919446099b219b08fa6cR53 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM - awesome stuff!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
Folks this work is amazing 🚀 I'm not extremely familiar with other implementations but as far as I know Hasura is one of the few to have this provided out of the box |
Implements #43
I'm creating this draft PR to get some feedback about this implementation.
How it works
Every
directive
adds apreExecution
hook.The hook will:
applyPolicy
for every FIELDS the directive is attached toapplyPolicy
function runs without the parent object and the input arguments (it should be fine)applyPolicy
result, the hook filters theGraphQLSchema
object by filtering the FIELDS or OBJECTS associated with the directive itselfpreExecution
functionNote that understanding if the query is an introspection one is necessary or the authorization errors will no more be visible.
Example:
@auth
QueryapplyPolicy
function@auth
FIELD are evicted from the schemanull
without the correspondingerror.
Todos
I think this PR requires many optimizations:
isIntrospection
functionisIntrospection
once (now it is executed one time for each directive)wrapSchema
filtering function once instead of one time for each directive. The main issue here is that the plugin is registered multiple timesapplyPolicy
memoization. it cannot be executed once due different parameters. Example@hasRole(role: "admin")
vs@hasRole(role: "reader")
Let me know your thought