Motivation
Support Authorization rules on interfaces
User Impact
Users will now be able to use @auth rules on interfaces and the implementing types will inherit those rules.
Implementation
Auth Rules on Interfaces.
We plan to support @auth
directive on interfaces.
@auth directive will work just like it works for types which are to provide authorization to perform query/update/delete on interfaces. The rules provided inside the @auth
directive on an interface will also be applied as an AND rule to those on the implementing types.
type Author {
id: ID!
name: String! @search(by: [hash])
posts: [Post] @hasInverse(field: author)
}
interface Post @auth(
query: { rule: """
query ($USER: String!) {
queryPost(filter: { author : { id: { eq: $USER } } } ) {
id
}
}"""
}
){
id: ID!
text: String @search(by: [fulltext])
datePublished: DateTime @search
author: Author!
}
type Question implements Post @auth(
query: { rule: """
query ($ANSWERED: Boolean!) {
queryQuestion(filter: { answered: $ANSWERED } ) {
id
}
}"""
}
){
answered: Boolean
}
type Answer implements Post @auth(
query: { rule: """
query ($USEFUL:Boolean!) {
queryAnswer(filter: { markedUseful: $USEFUL } ) {
id
}
}"""
}
){
markedUseful: Boolean
}
How auth works on the implementing types?
In the above example, the Question
and Answer
would automatically inherit the auth rules of the Post
type. This would mean that a user can only query a subset of questions and answers that are accessible through the queryPost
query and not anymore. We want to disallow the situation in which a user can query more posts through queryAnswer
or queryQuestion
than they can through queryPost
.
So a user would only be able to query the questions that they are the author of (auth rule coming from Post
) and which have the value of answered
coming from $ANSWERED
. A type would inherit the auth rules of all the interfaces that it implements and the final auth rules would be an AND of a types auth rules and of all the interfaces that it implements.
So in the generated schema the auth rules of Question
would look like
type Question implements Post @auth(
query: { and: [
{ rule: """
query ($USER: String!) {
queryPost(filter: { id: { eq: $USER } } ) {
id
}
}"""
}
{
rule: """
query ($ANSWERED: Boolean!) {
queryQuestion(filter: { answered: $ANSWERED } ) {
id
}
}"""
}]
}
){
id: ID!
text: String @search(by: [fulltext])
datePublished: DateTime @search
author: Author!
answered: Boolean
}
If there were more interfaces that the Question
type implements, then rules for those would also be added in an AND
condition to the auth rules of the `Question type.
How auth on interfaces works with all of this?
When it comes to applying auth rules on interfaces themselves, there we’ll have to do something different. We’ll have to do a union query where we query all the implementing types and apply the auth rules on them. Then the final query would be an OR query joining the results from all the implementing types. So
{
queryPost {
id
text
datePublished
}
}
would translate to a DQL query like the following
{
# Note all the auth rules for answers are applied here (these include the auth rules for a Post as well)
qa as queryAnswer(func: type(Answer)) {
}
# All the auth rules for a question are applied here (these include the auth rules for a Post as well)
qq as queryQuestion(func: type(Question) {
}
queryPost(func: uid(qa, qq)) {
id
text
datePublished
}
}
The above is a simplified example but this would work with filters, ordering and pagination as well.
Mutations on the interface will also work in the same manner. For example, in case of delete mutation on an interface, It will be broken into the delete mutation on implementing types and the nodes which satisfy auth rules of a corresponding implementing type + interface
will get deleted.