WIP: Auth rules custom DQL query

Motivation

Support for @auth rules with @custom DQL query.

User Impact

Users will now be able to use @auth rules provided in the Graphql schema with @custom DQL queries also. This will be a major feature in the @custom DQL and will enhance the user experience.

Implementation

Overview

Since @auth rules are defined in the GraphQL schema along with the type definition, we need to apply them to the DQL query based on the type queried in the DQL query. The problem reduces to finding the type of the query. After finding the type of the query, we apply auth rules and start finding the type of predicates and applying auth rules recursively.

Example

For the given schema:-

type Tweets {
	id: ID!
	text: String! @search(by: [fulltext])
	author: User
	timestamp: DateTime! @search
}
type User {
	screen_name: String! @id
	followers: Int @search
	tweets: [Tweets] @hasInverse(field: author)
}
type UserTweetCount @remote {
	screen_name: String
	tweetCount: Int
}

For this @custom DQL query:-

queryUserTweetCounts: [UserTweetCount] @custom(dql: """
	query {
		queryUserTweetCounts(func: type(User)) {
			screen_name: User.screen_name
			tweetCount: count(User.tweets)
		}
	}
	""")

The root type is User. In this query, we apply the @auth rules of type User at the root and then try to infer the types of predicates. In this query, both are scalar so the @auth rules are added at the root only.

But, for this @custom DQL var block of a query:-

var(func: type(Tweets)) @filter(anyoftext(Tweets.text, $search)) {
		Tweets.author {
			followers as User.followers
		}
		authorFollowerCount as sum(val(followers))
}

The root query type is Tweet. Now the predicate Tweets.author is of the type User and similarly User.followers is of the type User. The final var block after application of auth rules will look something like this:-

var(func: type(Tweets)) @filter(anyoftext(Tweets.text, $search))  @filter(TweetAuth){
		Tweets.author @filter(UserAuth) {
			followers as User.followers @filter(UserAuth)
		}
		authorFollowerCount as sum(val(followers))
}

In case if root func is not type(TypeName), then we try to find the type from filters. For example:-

me(func: has(Tweets.text)) {
  ...
}

Or

me(func: uid(0x1)) @filter(eq(Tweets.text, "some text")) {
    ...
}

In both the above queries, type can be deduced from the has function and eq filter respectively.

Limitations

There can be cases in which type is impossible to deduce from the existing approach. Some of the examples are:-

me(func: has(name@en)) {
 ...
}

or

me(func: uid(x, y) {
  ...
}

For these cases, we might need to introduce a directive in DQL to know which auth rules to apply to the query. For example:-

me(func: uid(x, y)  @auth(Tweets) {
 ....
}

In this case, the @auth directive tells us that auth rules of which type should be applied to the DQL query.

Please feel free to raise questions, and give suggestions on this topic.
cc: @pawan , @abhimanyusinghgaur

A couple of questions:

  • Will this be included in the 21.07 release?
  • Will this only be on @custom and not @lambda or the DQL interfaces?

Thanks.

Hi @eugaia,
It should be included in the 21.07 release.
This will be for only @custom DQL queries.

1 Like

@minhaj - Thanks!

1 Like

I am reading this for the first time to understand this in detail. I have a use case where I want to allow some content through a custom query with DQL that a user does not normally have access to under normal @auth rules. Will this still be possible?

Use Case Defined:

  • Users should not be able to see a list of all other users in the system
  • Admins in the application need to be able to manage users, so disabling queryUser and allowing only getUser is not a viable solution.
  • A User can share data with another user directly if they know their full name.
  • A user enters a first and last name in a field, the client parses this and runs a query to the custom query, the custom query uses DQL to bypass all auth rules to find a user that matches that full name. If the full name matches then return that user uid, no other information, only the uid.
  • Client can then use the uid returned to create an access node granting that uid to this user’s data nodes as he desires.

This whole use case is still in flux as I figure out how to do this better with auth rules (pending a few bug fixes in the works prayerfully.) I am thinking about allowing a user to set their own profile as public or not which allows other users to know they exists, or even allow a user to make their existence only known to a select set of users. But anyway I am trying to do this now I cross the point where the client needs to know if the referenced user actually exists to make themselves known to if not made known publicly.

This has been merged to master. See this PR for more details. We request the community to try it and present feedback and insights to us.

It would be great if there were graphql types in the examples, I’m not clear about whether TweetAuth maps to (and fetches auth rules from) type Tweet or type TweetAuth?

EDIT: This example references a User type and UserAuth, not sure whether UserAuth is a type that merely contains auth rules, or a name that triggers an auth look up on type User via a naming convention?

var(func: type(Tweets)) @filter(anyoftext(Tweets.text, $search))  @filter(TweetAuth){
		Tweets.author @filter(UserAuth) {
			followers as User.followers @filter(UserAuth)
		}
		authorFollowerCount as sum(val(followers))
}

from my understanding of this, the TweetAuth and UserAuth here are placeholders in the DQL script that get replaced with the actual auth rules for said type:

  • TweetAuth is the rules for type Tweet
  • UserAuth is the rules for type User

If you look at the schema the edge User.followers points to type Int in the schema but for this example to work it would actually point to [User] and that would then make sense adding on the placeholder filter to check for auth rules for the User type.

1 Like