defmodule MicrowavepropWeb.Api.AuthTest do use Microwaveprop.DataCase, async: true import Plug.Conn import Plug.Test alias Microwaveprop.Accounts alias Microwaveprop.AccountsFixtures alias MicrowavepropWeb.Api.Auth setup do user = AccountsFixtures.user_fixture() {:ok, {plaintext, token}} = Accounts.create_api_token(user, %{name: "T"}) %{user: user, plaintext: plaintext, token: token} end describe "init/1" do test "defaults to require mode" do assert Auth.init([]) == [mode: :require] end test "respects an explicit mode" do assert Auth.init(mode: :optional) == [mode: :optional] end end describe "call/2 in :require mode" do test "halts with 401 problem+json when no header is present" do conn = Auth.call(conn(:get, "/"), Auth.init([])) assert conn.halted assert conn.status == 401 assert get_resp_header(conn, "www-authenticate") == [~s(Bearer realm="api")] assert %{"detail" => "Missing bearer token in Authorization header."} = Jason.decode!(conn.resp_body) end test "halts when the header is malformed" do conn = :get |> conn("/") |> put_req_header("authorization", "Token abc") |> Auth.call(Auth.init([])) assert conn.status == 401 assert %{"detail" => "Authorization header must be `Bearer `."} = Jason.decode!(conn.resp_body) end test "halts when the bearer scheme is present but token is empty" do conn = :get |> conn("/") |> put_req_header("authorization", "Bearer ") |> Auth.call(Auth.init([])) assert conn.status == 401 end test "halts when the token is unknown / revoked" do conn = :get |> conn("/") |> put_req_header("authorization", "Bearer mwp_does-not-exist") |> Auth.call(Auth.init([])) assert conn.status == 401 assert %{"detail" => "Bearer token is invalid, expired, or revoked."} = Jason.decode!(conn.resp_body) end test "lets a valid token through and assigns user + token", %{user: user, plaintext: pt} do conn = :get |> conn("/") |> put_req_header("authorization", "Bearer #{pt}") |> Auth.call(Auth.init([])) refute conn.halted assert conn.assigns.current_api_user.id == user.id assert conn.assigns.current_api_token end test "accepts the lowercase `bearer` scheme too", %{plaintext: pt} do conn = :get |> conn("/") |> put_req_header("authorization", "bearer #{pt}") |> Auth.call(Auth.init([])) refute conn.halted end end describe "call/2 in :optional mode" do test "passes through when no token is present and assigns nil" do conn = Auth.call(conn(:get, "/"), Auth.init(mode: :optional)) refute conn.halted assert conn.assigns.current_api_user == nil assert conn.assigns.current_api_token == nil end test "halts when an invalid token is provided" do conn = :get |> conn("/") |> put_req_header("authorization", "Bearer mwp_bad") |> Auth.call(Auth.init(mode: :optional)) assert conn.halted assert conn.status == 401 end test "halts when the header is malformed in :optional mode" do conn = :get |> conn("/") |> put_req_header("authorization", "garbage") |> Auth.call(Auth.init(mode: :optional)) assert conn.halted assert conn.status == 401 end test "lets a valid token bind user + token", %{user: user, plaintext: pt} do conn = :get |> conn("/") |> put_req_header("authorization", "Bearer #{pt}") |> Auth.call(Auth.init(mode: :optional)) refute conn.halted assert conn.assigns.current_api_user.id == user.id end end end