Why my authorization rules can't work

What I want to do

I wanna make a list to control the auth in my system, but it can’t work.

Dgraph metadata

type UserAuth @auth(
    add: { rule: """
    query ($guid: String!) {
    queryUserAuth(filter: { mid: { eq: $guid },and:{ role:{eq:admin} } }) {
    id
    }
    }"""
    }
    update: { rule: """
    query ($guid: String!) {
    queryUserAuth(filter: { mid: { eq: $guid },and:{ role:{eq:admin} } }) {
    mid
    }
    }"""
    }
    delete: { rule: """
    query ($guid: String!) {
    queryUserAuth(filter: { mid: { eq: $guid },and:{ role:{eq:admin} } }) {
    mid
    }
    }"""
    }
) {
    mid : String! @id
    schools:[UserAuthSchoolClasses]
    role: UserAuthRole! @search
}

# Dgraph.Authorization {"VerificationKey":"xtjpk","Header":"x-d-token","Namespace":"xtjpk","Algo":"HS256","Audience":["user"]}

next is my token claims

{
    "aud": "user",
    "exp": 1661365141,
    "guid": "1",
    "iat": 1660285141,
    "sub": "user"
}

{
    "data": {
        "addUserAuth": null
    },
    "extensions": {
        "touched_uids": 4,
        "tracing": {
            "version": 1,
            "startTime": "2022-08-12T14:31:46.072776+08:00",
            "endTime": "2022-08-12T14:31:46.078711+08:00",
            "duration": 5935303,
            "execution": {
                "resolvers": [
                    {
                        "path": [
                            "addUserAuth"
                        ],
                        "parentType": "Mutation",
                        "fieldName": "addUserAuth",
                        "returnType": "AddUserAuthPayload",
                        "startOffset": 194572,
                        "duration": 5729831,
                        "dgraph": [
                            {
                                "label": "preMutationQuery",
                                "startOffset": 230193,
                                "duration": 1616877
                            },
                            {
                                "label": "mutation",
                                "startOffset": 1924047,
                                "duration": 1739284
                            },
                            {
                                "label": "query",
                                "startOffset": 0,
                                "duration": 0
                            }
                        ]
                    }
                ]
            }
        }
    }
}

Hi @Wan9xy,

Is this on a cloud instance? If not, please share the version of dgraph that you’re trying on.

How are you issuing the mutation? Can you please debug the headers and share them here?

1 Like

Maybe reading through this blog series might help

I am not seeing of hand where your given examples are wrong, but they are incomplete and I have not tried to duplicate your problem.

By the way… what is your problem, lol. What isn’t working?

in databse,the user “1” is exist, and it is admin role, when I add a new user token with the user “1”,it can’t add a new user.

yes, it’s on the cloud.

mutation {
  addUserAuth(input:{mid:"2",role:admin}) {
    userAuth {
      mid
    }
  }
}

up is my mutation,it can’t create a new user

and the jwt claims like the title.

OK, that’s almost enough info to go on. One final thing. Can you share how you’re making this call? Insomnia, Postman or maybe a web app? And however you’re doing that, can you reply with the value that you’re sending for the x-d-token header?

of course, I used it by postman.
x-d-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1c2VyIiwiZXhwIjoxNjYxOTUxNTM0LCJpYXQiOjE2NjA4NzE1MzQsInN1YiI6InVzZXIiLCJ4dGpwayI6eyJndWlkIjoiMSJ9fQ.OaCKkymAJ5uAxg94xFx5EbMSvfRpAxTtKLwBkTuDEfA

I took a look at that token value on jwt.io:

{
  "aud": "user",
  "exp": 1661951534,
  "iat": 1660871534,
  "sub": "user",
  "xtjpk": {
    "guid": "1"
  }
}

Double check your declaration of Dgraph.Authorization in the schema. I think your VerificationKey is incorrect. Have a look at the docs on what should be in there: https://dgraph.io/docs/graphql/authorization/authorization-overview/#dgraphauthorization-parameters

should this be:

type UserAuth @auth(
    add: { rule: """
    query ($guid: String!) {
    queryUserAuth(filter: { mid: { eq: $guid },and:{ role:{eq:admin} } }) {
    mid
    }
    }"""
    }

And given your jwt, your mutation and jwt is mismatched.

Should be:

mutation {
  addUserAuth(input:{mid:"1",role:admin}) {
    userAuth {
      mid
    }
  }
}

And I tested this which will result in:

{
  "data": {
    "addUserAuth": {
      "userAuth": [
        {
          "mid": "1"
        }
      ]
    }
  },
  ...

If you are using Dgraph Cloud, then you can use the GraphQL GUI to test this as well, you can put the jwt in the request headers like:

maybe my question is i wanna use user 1,he is a admin, he want to create a new user 2, and his role is admin too.

func main() {
	nowTime := time.Now()
	expireTime := nowTime.Add(300 * time.Hour)

	claims := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		//"USER": "2",
		//"guid": "2",
		"sub": "user",
		"aud": "user",
		"iat": nowTime.Unix(),
		"exp": expireTime.Unix(),
		"xtjpk": map[string]string{
			//"USER":    "2",
			"guid": "1",
			//"user_id": "1",
		},
		//"role": "nadmin",
	})
	signedString, err := claims.SignedString([]byte("xtjpk"))
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(signedString)
}

this is my method to make a token.

Change the structure to have a created by user field and make it required, then the add rule checks that the created by user is yourself which is an amin

sorry, I’m not understand, if i want to create a user, and the executor role must be a admin, how to write the rule. What is the rule’s access rule, I think when the query not null, then the mutation can pass.

So for example start with the schema:

type User {
  id: ID
  username: String! @id
  createdBy: User!
  role: Role! @search
}
enum Role {
  user
  admin
}

With this so far any new user has to have a createdBy user. You can create your first user now before building the auth rule.

mutation {
  addUser(input: {
    username: "foo",
    createdBy: { username: "foo" },
    role: admin
  }){ id }
}

Now you want to add a 2nd user (bar) but first let’s create a rule by writing a query that would traverse the Users back to you and check for the admin role needed to create the user:

query {
  queryUser @cascade {
    createdBy(filter: { username: { eq: "foo" }, role: admin }) {
      username # grab any property here
    }
  }
}

This ^ query with the cascade would get all users created by foo and only if foo was an admin.

Now to turn this into a rule we just remove the cascade directive and use variables:

type User @auth(
  add: """
    query ($username: String!) {
      queryUser {
        createdBy(filter: { username: { eq: $username }, role: admin }) {
          username # grab any property here
        }
      }
    }
  """
) {
  id: ID
  username: String! @id
  createdBy: User!
  role: Role! @search
}
enum Role {
  user
  admin
}

Now in your jwt, you don’t need your role for this rule as it will use the role stored in the db, you just need your username property and can then create a user like:

mutation {
  addUser(input: {
    username: "bar",
    createdBy: { username: "foo" },
    role: user
  }){ id }
}

The rule will be evaluated against the mutation as it would be queried after mutation hence allowing you to filter on the role of “foo”.

Does that clear it up?

Got it! thank u.
And the add rule’s rule, does it the query can search any thing, it can pass. because i have a requirement that the auth in level ‘3’. so i want figure out how it works. I’m silly, so sorry :joy:

Yes, anything that is searchable/indexed in a regular GraphQL query can be searchable in a rule.

But now the question is my query rule can search target, the query’s response not null, why it can’t create a new user :joy:

What is your schema (including rules) and jwt?

type UserAuth {
    mid : String! @id
    schools:[UserAuthSchoolClasses]  @hasInverse(field:"user")
    role: UserAuthRole! @search
}

type UserAuthSchoolClasses {
    id: ID!
    school:School! @hasInverse(field:"administrator")
    school_full_controller:Boolean! @search
    categories:[Category]
    user: UserAuth! @hasInverse(field:"schools")
}
"""班级"""
type Classes implements Base
@auth(
    add: { rule:"""
    query ($user_id: String!,$school: String!,$categories: String){
    queryClasses {
    id
    school(filter:{id:$school}){
    id
    administrator(
    filter: {
    or: [{ has: categories }, { school_full_controller : true }]
    }
    ) {
    id
    user(filter:{mid:{eq:$user_id}}) {
    mid
    }
    school_full_controller
    categories (filter:{id:$school}){
    id
    }
    }
    }
    }
    }
    """
    }
) {
    id: ID!
    name: String! @search
    year: Int! @search
    class_type: ClassType!
    school: School! @hasInverse(field: "classes")
    recruiting_type: RecruitingType!
    is_full_day: Boolean! @search
    start_date: DateTime! @search
    end_date: DateTime! @search
    course_type: CourseType!
    exam_type: ExamType!
    category: Category!
    lend_teacher: Boolean!
    courses: [Course] @hasInverse(field: "classes")
    course_arrange: String
}

"""课程"""
type Course implements Base {
    id: ID!
    name: String! @search
    subject: Subject!
    school: School!
    classes: Classes! @hasInverse(field: "courses")
    sections: [CourseSection] @hasInverse(field: "course")
    remark: String
    finish: Boolean! @search
    start_date: DateTime! @search
    end_date: DateTime! @search
    introduce: String
    virtual: Boolean!
    lend_teacher: Boolean!
    share: Boolean!
    merge: Boolean!
    people_num: Int
}

"""节"""
type CourseSection implements Base {
    id: ID!
    name: String! @search
    course: Course! @hasInverse(field: "sections")
    identity: String!
    date: DateTime! @search
    start_time: DateTime! @search
    end_time: DateTime! @search
    teacher: [TeacherCourseInfo]
    time_duration: Float
    is_full_day: Boolean!
}

it’s the schema, jwt like this:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ1c2VyIiwiZXhwIjoxNjYyMjU1ODcxLCJpYXQiOjE2NjExNzU4NzEsInN1YiI6InVzZXIiLCJ4dGpwayI6eyJjYXRlZ29yaWVzIjoiMHgwMDAyIiwic2Nob29sIjoiMHgwMDAxIiwidXNlcl9pZCI6IjEifX0.HNcDrdF78Eu0uGFzuyncQd4Sx4JyG7ChkHF6Ha-lyKQ

actually, schools and categories shouldn’t pass to graphQL with jwt, but i can’t find a better method to pass it.