How to validate ownership in an action

Hi, I’m currently building a toy chat app to learn Ash (a blog post is on the way) and I’m trying to understand where to put validations/authorizations for an action. This is what I’m trying to do:

A User can join a Room if it is not private OR if the User is the Room’s owner. What is the Ash way to check ownership?

  1. Do I compare the :user argument in the :join_room action to the owner?
  2. Do I use the actor in a custom validation/policy?
  3. Is there another way I haven’t thought of yet?

This is my shortened Room resource:

defmodule Chatter.Chat.Room do
 actions do
   defaults [:read, :destroy]

   update :join_room do
     argument :user, :map, allow_nil?: false

     validate attribute_equals(:private, false) do
       message "This room is private"
     end

     change manage_relationship(:user, :users, type: :create)
   end
 end

 attributes do
   integer_primary_key :id

   attribute :name, :string, allow_nil?: false
   attribute :private, :boolean, default: false

   create_timestamp :created_at
   update_timestamp :updated_at
 end

 relationships do
   many_to_many :users, User do
     through RoomUser
     source_attribute_on_join_resource :room_id
     destination_attribute_on_join_resource :user_id
   end
   has_many :messages, Message
   belongs_to :owner, User, allow_nil?: false
 end
end

I call the action through the domain’s code interface in a Livevew: Chat.join_room(room, current_user).

Your options 1/2 are the way in general, yes :slight_smile:

Validations

A custom validation can be added ( validations can be modules that implement the Ash.Resource.Validation behavior) to do things like check against the owner.

Policies

Depending on how you view this class of issue (i.e is it validity or is it security), policies may be in order, and I would likely go that route based on what I’m seeing.

policies do
  policy action_type(:read) do
    description "users can see all available rooms"
    policy authorize_if always()
  end

  policy action_type(:create) do
    description "users can create rooms that they own"
    authorize_if relating_to_actor(:owner)
  end

  policy action(:join_room) do
    description "users can join their own rooms, or public rooms"
    authorize_if relates_to_actor_via(:owner)
    authorize_unless attribute_equals(:private, true)
  end

  policy action_type(:destroy) do
    description "users can delete their own rooms"
    authorize_if relates_to_actor_via(:owner)
  end
end
2 Likes

Thank you very much, the policies look like the right way to do it!

I tried to implement your solution in Ash 3.0.16 and seems there’s no way to authorize depending on a data’s attribute like in your policy for :join_room.

There is no function attribute_equals/2 only actor_attribute_equals/2. I also searched in the docs and they mention attribute/2 but it also doesn’t work.

I got it to work like this for now:

  policies do
    policy action([:public_rooms]) do
      description "users can see all public rooms"
      authorize_if expr(private == false)
    end
  end

And just a check to make sure I understand the difference between relates_to_actor_via(:owner) and relating_to_actor(:owner): The first one looks for an existing relationship :owner and fails if it’s not found. The second function looks for a newly made (or changing?) relationship in the action and fails if nothing is changed. Do I understand that right?

Thank you again for your help, I really appreciate it.

Ah, right sorry, the expression policy is the way to go there :+1:

And yes relates_to_actor_via means “currently relates to the actor with this relationship”, and relating_to_actor_via means “is modifying the relationship to point to the actor”

1 Like