GraphQL Auth on predicates

Hi,
Is this possible to solve by GraphQL Auth?

type Todo {
    id: ID!
    text: String!
    owner: User
}
type User {
    status: Int! # allowed to mutate only by the admin (so, not by User)
    username: String! @id 
    todos: [Todo] @hasInverse(field: owner) # allowed to mutate by the owner/user (this is explained by the doc)
    }
}

Yup. See:

J

Thanks, but in all those examples the @auth scope is the whole object. How I can scope @auth on the field ‘status’ from my example? In other words, I as admin of my application would like to prevent the user to change his ‘status’ field but allow him to add Todo.

I’m assuming you have more fields for the User type. Because you could lock down this type with @auth rules to only allow mutate by admin which would make the username and status only mutable by an admin and then allow the Todo type to be created/updated/deleted by the direct owner which with the hasInverse directive will keep the edges balanced. So yes, this very simply example is possible you just have to add/update/delete the Todo and not update the User.

What is not possible without some linking-node work arounds:

type User {
  username: String! @id
  status: Int! # allowed to mutate _only_ by the admin
  nickname: String # allowed to mutate by the user _or_ the admin
}
1 Like

You pretty much can only do what you want right now with a custom mutation. This is IMHO the biggest missing feature with DGraph.

J

1 Like

Now, I have to make an object:

type User {
    username: String! @id
    nickname: String
    adminFields: Administration!
}
type Administration {
    status: Int!
}

I do not know if this is a solution since it is not possible to filter the nodes by the edge. So, how the ‘queryUser’ auth query would look like to allow an update for the user with status 0?

1 Like

:grimacing:

work arounds to situations like this do indeed create additional problems that need even more workarounds.

type User @auth(
  update: { OR: [
    # Rule to let users update the user if the Administration status is == 0
    { rule: """
      query ($USERNAME: String!) {
        queryUser(filter: { username: { eq: $USERNAME } }) {
          adminFields(filter: { status: { eq: 0 } }) {
            status
          }
        }
      }
      """ },
    # Rule to let admin role update all users
    { rule: "{$ROLE: {eq:\"Admin\"}}" }
  ] }
) {
    username: String! @id
    nickname: String
    adminFields: Administration!
}
type Administration {
    status: Int! @search
}

Note this is not the most efficient method and it would be better if we could do:

1 Like

Thanks.
The interface auth would be a nice solution, but the object inherits auth from the implemented interface to my disappointment.

What would the efficient solution be? Since the admin-controlled ‘status’ predicate is very important and is mandatory to most of the nodes?

I can see 2 options:

  1. make the new type UserStatus, then modify user object as User.status: UserStatus!
  2. move all user-controllable data to different type:
type User {
  status: Int!
  profile: UserProfile! @hasInverse(field: user)
}

type UserProfile {
  user: User! 
  username: String! @id
  nickname: String
}

and then User type would be modified by admin, while UserProfile - by the user.

Referencing:

So first the inefficiency explained. Auth rules work by checking for the existence of the inner fields which under the hood uses the same process as @cascade so if all fields requested in the rule exist then it equates to true.

But @cascade is inefficient because it is a post query process in the query plan. There is a query plan even though there is no query planner at the moment. This is just understood that the query always follows the same plan, maybe some day we will have a DQL query planner that will make this better. See for reference: Query Planner. Back to the topic at hand. The main problem with cascade is that you could have a rule that queries ALL nodes of a type with deep edges to appease the rule, when more efficiently would be to write the most efficient query to get a truthy value.

In example:

Data:

_:foo <dgraph.type> "User" .
_:bar <dgraph.type> "User" .
_:baz <dgraph.type> "User" .
_:foo <User.status> _:status_1.
_:status_1 <dgraph.type> "Status" .
_:status_1 <Status.status> "1" .
_:status_1 <Status.for> _:foo .
_:bar <User.status> _:status_2.
_:status_2 <dgraph.type> "Status" .
_:status_2 <Status.status> "0" .
_:status_2 <Status.for> _:bar .
_:status_2 <Status.for> _:baz .

Take these triples and imagine we had a few more million users to go along with it, but only these two status nodes. If you wanted to efficiently query the users with the status equal to 0 you would not start at the users, traverse to the filtered status and then post query process to filter those users out. No, instead to be efficient you would start with the smallest universe which would be the filtered status and traverse to the users, and then use those users to appease the rule.

This really gets nitty gritty though because depending on how in GraphQL you filter your query and depending on the data layout, this might actually be less efficient.

So again I reference the link above to the query planner if you want more of my thoughts about the complexity of actually making queries more efficiently which depends on the data, and how do you know the data until you make the query. :exploding_head: :loop:

hehe, you asked for efficiency and @amaster507 answered…

This is the same thing 1) and 2) here.

This will not work as expected, since you cannot prevent the @hasInverse field from being updated, which keeps the app unsecured.

Your only workaround here is to create a custom mutation that can just update the user information with dql, and lock the whole field otherwise to admins-only. This is why @zmajew is completely right on this thread.

J