Improving access permissions, Pow Authentication

I am in need of help to create a role in my access rules.
can you help me?
I currently have Pow-based authentication
However, I need to improve these access permissions.
I need every standard user (user) to be able to change only his own records.
Below is my files

MyprojectWeb.EnsureRolePlug

defmodule MyprojectWeb.EnsureRolePlug do
  @moduledoc """
  This plug ensures that a user has a particular role.

  ## Example

      plug MyprojectWeb.EnsureRolePlug, [:user, :admin]

      plug MyprojectWeb.EnsureRolePlug, :admin

      plug MyprojectWeb.EnsureRolePlug, ~w(user admin)a
  """
  import Plug.Conn, only: [halt: 1]

  alias MyprojectWeb.Router.Helpers, as: Routes
  alias Phoenix.Controller
  alias Plug.Conn
  alias Pow.Plug

  @doc false
  @spec init(any()) :: any()
  def init(config), do: config

  @doc false
  @spec call(Conn.t(), atom() | binary() | [atom()] | [binary()]) :: Conn.t()
  def call(conn, roles) do
    conn
    |> Plug.current_user()
    |> has_role?(roles)
    |> maybe_halt(conn)
  end

  defp has_role?(nil, _roles), do: false
  defp has_role?(user, roles) when is_list(roles), do: Enum.any?(roles, &has_role?(user, &1))
  defp has_role?(user, role) when is_atom(role), do: has_role?(user, Atom.to_string(role))
  defp has_role?(%{role: role}, role), do: true
  defp has_role?(_user, _role), do: false

  defp maybe_halt(true, conn), do: conn
  defp maybe_halt(_any, conn) do
    conn
    |> Controller.put_flash(:error, "Acesso não autorizado!")
    |> Controller.redirect(to: Routes.dashboard_path(conn, :index))
    |> halt()
  end
end

In the controller
plug MyprojectWeb.EnsureRolePlug, :admin when action in [:index, :new, :show, :edit, :update, :delete]

So the question is
How do I make the standard user access only his own records and not be able to change another user?
Could help me, with any example?

1 Like

You can take a look at canada package https://github.com/jarednorman/canada to have an idea.
Typically you have to check if current user’s id matches the owner id (like user_id or owner_id) of the record.

There are many ways of dealing with this and I concur with @NeutronStein’s suggestion to check out Canada.

I would normally do this within the context of the resource.

E.g. I add this to the resource context:

defmodule MyApp.UserRecords do
  # ...

  @spec can_access?(%MyApp.UserRecords.UserRecord{}, %MyApp.Users.user{}) :: boolean()
  def can_access?(%{user_id: id}, %{id: id}), do: true
  def can_access?(_user_record, _current_user), do: false
end

And then in the controller check this when I load the record:

defmodule MyAppWeb.UserRecordsController do
  # ...

  plug :load_user_record when action in [:show, :edit, :update, :delete]

  # ...

  defp load_resource(conn, _opts) do
    with {:ok, resource} <- MyApp.UserRecords.get(conn.params["id"]),
            true         <- MyApp.UserRecords.can_access?(resource, Pow.Plug.current_user(conn)) do
      assign(conn, :user_record, resource)
    else
      _any ->
        conn
        |> put_flash(:error, "Record doesn't exist")
        |> redirect(to: Routes.user_record_path(conn, :index))
    end
  end