ueberauth/guardian

Permissions not halting on unauthorized

Lazarus404 opened this issue · 1 comments

Okay, so, bearing in mind I'm upgrading from 0.14 to 2.0, so I may be doing something wrong. However, I have a permissions check at the top of each controller (different permissions for different actions). This may look like:

plug(Permissions, [one_of: [%{sys: [:sys]}]] when action in [:create, :update])

Now, in the :create action, it might look like:

def create(conn, params, _current_user, _claims) do
  with {:ok, %Account{id: id}} <- Repo.transaction(fn -> create_account(params) end) do
    conn
    |> put_status(:created)
    |> json(%{status: "success", id: id})
  else
    e -> do_error(conn, e)
  end
end

However, when running my test:

test "POST /api/account DOES NOT create a new account for non-sys user", ctx do
  conn =
    build_conn()
    |> MyApp.Guardian.Plug.sign_in(ctx.admin, %{pem: User.perms(ctx.admin, :manager)})
    |> post("/api/account", @valid_params)

  assert conn.status == 401
end

I see the unauthorised error printed in the console, but then the action gets run, anyway:

{:unauthorized, :insufficient_permission}

  1) test creating accounts POST /api/account DOES NOT create a new account for non-sys user (MyApp.AccountControllerTest)
     test/api_web/controllers/account_controller_test.exs:99
     ** (Plug.Conn.AlreadySentError) the response was already sent
     code: |> post("/api/account", @valid_params)
     stacktrace:
       (plug) lib/plug/conn.ex:372: Plug.Conn.put_status/2
       (api) lib/api_web/controllers/account_controller.ex:45: MyAppWeb.AccountController.create/5

What I would expect is for the Permission check to halt the Plug stack. Do I have to add anything additional to the Permission call, perhaps?

Dagnabbit. I found the answer from my own questioning :)

For those that read this with the same issue, you need to add halt to your Error Handler. For instance:

defmodule MyApp.AuthErrorHandler do
  import Plug.Conn

  @behaviour Guardian.Plug.ErrorHandler

  @impl Guardian.Plug.ErrorHandler
  def auth_error(conn, {type, reason}, _opts) do
    body = Poison.encode!(%{message: to_string(type)})
    conn
      |> put_resp_content_type("application/json")
      |> send_resp(401, body)
      |> halt()
  end
end