Really have been digging into performance of our auth rules this week trying to optimize everything I can on my end.
I have a schema with auth rules such as:
type User @auth(
query: { or: [
{ rule: "{$USERROLE: { eq: \"ADMINISRATOR\" }}" }
{ rule: "query ($USERNAME: String!) { queryUser(filter: { username: { eq: $USERNAME } }) { username } }" }
]}
add: { rule: "{$USERROLE: { eq: \"ADMINISRATOR\" }}" }
update: { rule: "{$USERROLE: { eq: \"ADMINISRATOR\" }}" }
delete: { rule: "{$USERROLE: { eq: \"ADMINISRATOR\" }}" }
) {
username: String! @id
name: String!
hasNotes: [Note] @hasInverse(field: by)
}
type Note @auth(
query: { or: [
{ rule: "{$USERROLE: { eq: \"ADMINISRATOR\" }}" }
{ rule: "query ($USERNAME: String!) { queryNote { user(filter: { username: { eq: $USERNAME } }) { username } } }" }
]}
add: { or: [
{ rule: "{$USERROLE: { eq: \"ADMINISRATOR\" }}" }
{ rule: "query ($USERNAME: String!) { queryNote { user(filter: { username: { eq: $USERNAME } }) { username } } }" }
]}
update: { or: [
{ rule: "{$USERROLE: { eq: \"ADMINISRATOR\" }}" }
{ rule: "query ($USERNAME: String!) { queryNote { user(filter: { username: { eq: $USERNAME } }) { username } } }" }
]}
delete: { or: [
{ rule: "{$USERROLE: { eq: \"ADMINISRATOR\" }}" }
{ rule: "query ($USERNAME: String!) { queryNote { user(filter: { username: { eq: $USERNAME } }) { username } } }" }
]}
) {
id: ID
content: String
by: User!
}
The concept here is that an administrator would have access to everything globally while a non admin regular user would have full access of their notes and read only access to their own user.
This works. But…
This method uses cascade theoretically on the query rules. Doing this selects ALL of the notes and then reduces from there to only the ones that have the Note.by predicate with the filtered edge.
A more efficient way to make the same query would be to reverse the query and first select the User and then use the User.hasNotes edge unfiltered. This would
- reduce the footprint at root significantly reducing query times, and
- remove a filter from an edge which reduces query times.
I understand that this would change how the query rule was applied to the query because right now it is expecting the root to be the same type to where it is applied. To this I have two possible solutions:
- Allow a user to define where the type match root is. As auth rules are not stacked on one another anyways, a query rule is querying against an unrestricted dataset. If a user could specify the place where the uid is expected then the dql conversion of the query could use that to tie it to the root type.
@auth( query: { rule: """
query ($USERNAME: String!) {
getUser(username: $USERNAME) {
hasNotes {
id @here
}
}
}
""" })
- Allow an advanced user to use DQL in auth rules. This would be really nice
a. Propose changingrule
usage in a breaking change whererule
only applies to RBAC
b. Propose adding propquery
which accepts a GraphQL query with the current type restrictions applied
c. Propose adding propdql
which accepts a DQL query for advanced users
@auth( query: { dql: """
query queryNote ($USERNAME: string!) {
var(func: eq(User.username, $USERNAME)) {
notes as Contact.hasNotes
}
queryNotes(func: uid(notes))
}
""" })
NOTE: I am not 100% sure of my DQL syntax, I have never used graphql variables, as they are called, in DQL