rajska
rajska copied to clipboard
Proposal: add new special `has_user_access?` for QueryAuthorization
Today, the has_user_access?/3
receives the user, some struct and the rule. This is nice for the Rajska.ObjectScopeAuthorization
where the struct received is "complete" with all data, since it's coming from the source object that is being authorized.
But, for the Rajska.QueryAuthorization
it's a bit strange, since we receive an "incomplete" struct, created using the scope
and args
option. This can sometimes be confusing, since we need to know if this struct is coming from the query authorization or the object authorization.
This can even limit (or force creation of workarounds), when the arguments received in the query or mutation is not directly related to the scope module. Consider this example:
Post and a Comment are Ecto structs, with Post having a has_many :comments, Comment
.
object :post do
field :id, :integer
field :name, :string
field :comments, list_of(:comments)
end
object :comment do
field :id, :integer
field :text, :string
field :post, :post
end
field :list_posts_by_comments, list_of(:post) do
arg :comment_ids, list_of(:integer)
middleware Rajska.QueryAuthorization, [
permit: :user,
scope: Post,
args: %{?????????: :comment_ids},
]
resolve &BlogResolver.list_posts/2
end
In this case, the Post struct does not contains a comment_ids
field, so we have to use the most related field form post, like this: args: %{comments: :comment_ids}
or create a virtual field comment_ids
in Post.
It's doable, but seems like a workaround.
My suggestions:
- have another function (besides
has_user_access?
) just for the QueryAuthorization, with a signature more or less likequery_authorized?(%User{}, Post = _just_the_module, %{comment_ids: comment_ids} = args, rule)
- use the same
has_user_access?
, but add a fourth argument, acting like a metadata and pass the empty struct:has_user_access?(%User{}, %Post{} = _no_field_filled, rule, %{comment_ids: comment_ids} = metadata)
I don't think these 2 suggestions are the best API, but couldn't think a better one right now. If you guys agree with the proposal, we should probably iterate to find a more suitable API for the QueryAuthorization.
I agree that it's annoying, but I also quite like having the same API for Query and Object authorization. However, now it's a "same-but-not-really" API, so I would also be in favor of changing it, but aiming for something that will work for both middlewares. Personally, the first option seems more intuitive since it matches the middleware declaration more closely.
Good point! Having the same API for both is better.
If we go with the first approach, what should the third argument (the parameters arg) have as data? I was thinking something like:
%{
authorization_type: :query | :object,
query_or_mutation: :list_users,
object: %Post{},
query_args: %{comment_ids: ...},
}
-
authorization_type
: useful if the user needs to know from where the function is being called -
query_or_mutation
: the query/mutation name -> same from above, if user needs information from where it's coming from -
object
: the object when the function is being called for object authorization, -
query_args
: the args that come from the query when doing query authorization. We can keep the API we have today that selects which args the struct is build with, so we standardize parameters. For example, the query arg could come asid
in some queries/mutation andpost_id
in others, so we can have something likeargs: %{id: :post_id}
to "transform" arguments
Is something like this you have in mind?